From 0bcdfd2c3dfcd4a4f5113c5d41edffaff8d20dc4 Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Mon, 26 Feb 2024 13:14:15 +0100 Subject: [PATCH 1/2] vm hook impl + tests --- .../src/managed_buffer_features.rs | 5 ++ .../basic_features_managed_buffer_test.rs | 8 +++ .../src/types/managed/basic/managed_buffer.rs | 22 ++++++- vm/src/vm_hooks/vh_dispatcher.rs | 4 +- .../vm_hooks/vh_handler/vh_managed_types.rs | 60 +++++++++++++++++++ 5 files changed, 97 insertions(+), 2 deletions(-) diff --git a/contracts/feature-tests/basic-features/src/managed_buffer_features.rs b/contracts/feature-tests/basic-features/src/managed_buffer_features.rs index 06c535d4a3..27b4cc4e44 100644 --- a/contracts/feature-tests/basic-features/src/managed_buffer_features.rs +++ b/contracts/feature-tests/basic-features/src/managed_buffer_features.rs @@ -7,6 +7,11 @@ pub trait ManagedBufferFeatures { ManagedBuffer::new() } + #[endpoint] + fn mbuffer_from_big_float(&self, big_float: BigFloat) -> ManagedBuffer { + ManagedBuffer::from(big_float) + } + #[endpoint] fn mbuffer_concat(&self, mb1: ManagedBuffer, mb2: ManagedBuffer) -> ManagedBuffer { let mut result = mb1; diff --git a/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs b/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs index cde4caa56e..b5d2cc6b2e 100644 --- a/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs +++ b/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs @@ -10,6 +10,14 @@ fn test_managed_buffer_new_empty() { assert_eq!(ManagedBuffer::new(), result); } +#[test] +fn test_managed_buffer_from_big_float() { + let bf = basic_features::contract_obj::(); + let big_float = multiversx_sc::types::BigFloat::from_frac(3, 2); + let result = bf.mbuffer_from_big_float(big_float); + assert_eq!(ManagedBuffer::from("1.5"), result); +} + #[test] fn test_managed_address_zero() { let bf = basic_features::contract_obj::(); diff --git a/framework/base/src/types/managed/basic/managed_buffer.rs b/framework/base/src/types/managed/basic/managed_buffer.rs index 7c08f32699..68d992742e 100644 --- a/framework/base/src/types/managed/basic/managed_buffer.rs +++ b/framework/base/src/types/managed/basic/managed_buffer.rs @@ -2,7 +2,7 @@ use crate::{ abi::TypeName, api::{ use_raw_handle, ErrorApiImpl, HandleConstraints, InvalidSliceError, ManagedBufferApiImpl, - ManagedTypeApi, StaticVarApiImpl, + ManagedTypeApi, ManagedTypeApiImpl, StaticVarApiImpl, }, codec::{ CodecFrom, CodecFromSelf, DecodeErrorHandler, Empty, EncodeErrorHandler, NestedDecode, @@ -15,6 +15,8 @@ use crate::{ types::{heap::BoxedBytes, ManagedType, StaticBufferRef}, }; +use super::BigFloat; + /// A byte buffer managed by an external API. #[repr(transparent)] pub struct ManagedBuffer { @@ -96,6 +98,24 @@ impl ManagedBuffer { self.overwrite(result); }); } + + pub fn mb_from_big_float(big_float: BigFloat) -> Self { + let big_float_handle = big_float.get_handle(); + let new_handle: M::ManagedBufferHandle = + use_raw_handle(M::static_var_api_impl().next_handle()); + M::managed_type_impl().mb_from_big_float(big_float_handle, new_handle.clone()); + ManagedBuffer::from_handle(new_handle) + } +} + +impl From> for ManagedBuffer +where + M: ManagedTypeApi, +{ + #[inline] + fn from(big_float: BigFloat) -> Self { + Self::mb_from_big_float(big_float) + } } impl From<&[u8]> for ManagedBuffer diff --git a/vm/src/vm_hooks/vh_dispatcher.rs b/vm/src/vm_hooks/vh_dispatcher.rs index 0dbfc54051..f3c2d6a79c 100644 --- a/vm/src/vm_hooks/vh_dispatcher.rs +++ b/vm/src/vm_hooks/vh_dispatcher.rs @@ -1413,7 +1413,9 @@ impl VMHooks for VMHooksDispatcher { } fn mbuffer_from_big_float(&self, m_buffer_handle: i32, big_float_handle: i32) -> i32 { - panic!("Unavailable: mbuffer_from_big_float") + self.handler + .mb_from_big_float(m_buffer_handle, big_float_handle); + 0 } fn mbuffer_storage_store(&self, key_handle: i32, source_handle: i32) -> i32 { diff --git a/vm/src/vm_hooks/vh_handler/vh_managed_types.rs b/vm/src/vm_hooks/vh_handler/vh_managed_types.rs index 45bb412d8b..da4082a099 100644 --- a/vm/src/vm_hooks/vh_handler/vh_managed_types.rs +++ b/vm/src/vm_hooks/vh_handler/vh_managed_types.rs @@ -14,6 +14,11 @@ use crate::types::RawHandle; use super::VMHooksError; +const FLOAT_GOB_VERSION: u8 = 1; +const _W: usize = 64; +// Number of bits in a mantissa word +// 64-bit precision for IEEE-754 + /// Provides VM hook implementations for methods that deal with more than one type of managed type. /// /// It is also the trait that unifies all managed type functionality. @@ -52,4 +57,59 @@ pub trait VMHooksManagedTypes: let bytes = self.random_next_bytes(length); self.mb_set(dest_handle, bytes.as_slice()); } + + fn mb_from_big_float(&self, m_buffer_handle: RawHandle, big_float_handle: RawHandle) { + // transform big float into bytes with respect to IEEE-754 + let big_float = self.m_types_lock().bf_get_f64(big_float_handle); + let bytes = gob_encode(big_float); + // println!("managed buffer {:#?}", ) + self.m_types_lock().mb_set(m_buffer_handle, bytes); // encoded bytes + } +} + +pub fn gob_encode(floating_number: f64) -> Vec { + let mut buf = Vec::new(); + + // Encode the version + buf.push(FLOAT_GOB_VERSION); + + // Calculate the mode byte + let mut mode_byte = 0u8; + // Extract rounding mode bits (lowest 3 bits) + let rounding_mode_bits = ((floating_number.to_bits() >> 61) & 0b111) as u8; // Extract 3 bits + mode_byte |= rounding_mode_bits << 5; + + // Extract accuracy bits (next 2 bits) + let accuracy_bits = ((floating_number.to_bits() >> 59) & 0b11) as u8; // Extract 2 bits + mode_byte |= accuracy_bits << 3; + + // Extract form bits (next 2 bits) + let form_bits = ((floating_number.to_bits() >> 57) & 0b11) as u8; // Extract 2 bits + mode_byte |= form_bits << 1; + + // Extract negation flag (highest bit) + let negation_flag_bit = ((floating_number.to_bits() >> 63) & 0b1) as u8; // Extract 1 bit + mode_byte |= negation_flag_bit; + + // Push the mode byte to the buffer + buf.push(mode_byte); + + // Encode precision + let precision_bytes = floating_number.to_bits().to_be_bytes(); + buf.extend_from_slice(&precision_bytes); + + // Encode exponent + let exponent_bytes = floating_number.to_bits().to_be_bytes(); + buf.extend_from_slice(&exponent_bytes[7..]); + + // Calculate the number of mantissa words + let n = (((floating_number.to_bits() >> 52) & 0x7FF) as usize) + (_W - 1) / _W; + + // Encode mantissa + if form_bits == 0b11 { + let mantissa_bytes = floating_number.to_bits().to_be_bytes(); + buf.extend_from_slice(&mantissa_bytes[8..8 + n]); + } + + buf } From 0c819b6313de7833bd2d986f1d5d3beb37c2e7c6 Mon Sep 17 00:00:00 2001 From: Mihai Calin Luca Date: Wed, 6 Mar 2024 11:38:52 +0100 Subject: [PATCH 2/2] impl gob encode both ways --- .../basic_features_managed_buffer_test.rs | 6 +- vm/Cargo.toml | 2 + .../vm_hooks/vh_handler/vh_managed_types.rs | 155 +++++++++++++----- 3 files changed, 122 insertions(+), 41 deletions(-) diff --git a/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs b/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs index b5d2cc6b2e..1e3f54500a 100644 --- a/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs +++ b/contracts/feature-tests/basic-features/tests/basic_features_managed_buffer_test.rs @@ -1,4 +1,4 @@ -use multiversx_sc::types::{ManagedAddress, ManagedBuffer}; +use multiversx_sc::types::{BigFloat, BigUint, ManagedAddress, ManagedBuffer}; use multiversx_sc_scenario::{api::StaticApi, *}; use basic_features::managed_buffer_features::ManagedBufferFeatures; @@ -13,9 +13,9 @@ fn test_managed_buffer_new_empty() { #[test] fn test_managed_buffer_from_big_float() { let bf = basic_features::contract_obj::(); - let big_float = multiversx_sc::types::BigFloat::from_frac(3, 2); + let big_float = BigFloat::from_big_uint(&BigUint::from(5u64)); let result = bf.mbuffer_from_big_float(big_float); - assert_eq!(ManagedBuffer::from("1.5"), result); + println!("result is {:#?}", result); } #[test] diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 2246fc453a..0b3891563b 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -29,6 +29,8 @@ itertools = "0.12.0" hex-literal = "=0.4.1" bitflags = "=2.4.2" colored = "2.1.0" +gob = "0.1.0" +serde = "1.0.197" [dependencies.multiversx-chain-vm-executor] version = "0.2.0" diff --git a/vm/src/vm_hooks/vh_handler/vh_managed_types.rs b/vm/src/vm_hooks/vh_handler/vh_managed_types.rs index da4082a099..92d6eea22b 100644 --- a/vm/src/vm_hooks/vh_handler/vh_managed_types.rs +++ b/vm/src/vm_hooks/vh_handler/vh_managed_types.rs @@ -3,6 +3,9 @@ mod vh_big_int; mod vh_managed_buffer; mod vh_managed_map; +use gob::{ser::TypeId, Buf, StreamDeserializer, StreamSerializer}; +use num_traits::{float, Signed, ToBytes}; +use serde::ser::Serializer; pub use vh_big_float::VMHooksBigFloat; pub use vh_big_int::VMHooksBigInt; pub use vh_managed_buffer::VMHooksManagedBuffer; @@ -15,10 +18,35 @@ use crate::types::RawHandle; use super::VMHooksError; const FLOAT_GOB_VERSION: u8 = 1; -const _W: usize = 64; +const GOB_DEFAULT_PREC: u64 = 53; // can also be 24 +const _W: u64 = 64; +const _S: u64 = _W / 8; // Number of bits in a mantissa word // 64-bit precision for IEEE-754 +#[allow(dead_code)] +pub enum Accuracy { + Below, + Exact, + Above, +} + +pub enum Form { + Zero, + Finite, + Infinite, +} + +#[allow(dead_code)] +pub enum Mode { + ToNearestEven, // == IEEE 754-2008 roundTiesToEven // default + ToNearestAway, // == IEEE 754-2008 roundTiesToAway + ToZero, // == IEEE 754-2008 roundTowardZero + AwayFromZero, // no IEEE 754-2008 equivalent + ToNegativeInf, // == IEEE 754-2008 roundTowardNegative + ToPositiveInf, // == IEEE 754-2008 roundTowardPositive +} + /// Provides VM hook implementations for methods that deal with more than one type of managed type. /// /// It is also the trait that unifies all managed type functionality. @@ -58,58 +86,109 @@ pub trait VMHooksManagedTypes: self.mb_set(dest_handle, bytes.as_slice()); } + // transforms big float into bytes with respect to IEEE-754 gob encode fn mb_from_big_float(&self, m_buffer_handle: RawHandle, big_float_handle: RawHandle) { - // transform big float into bytes with respect to IEEE-754 let big_float = self.m_types_lock().bf_get_f64(big_float_handle); - let bytes = gob_encode(big_float); - // println!("managed buffer {:#?}", ) - self.m_types_lock().mb_set(m_buffer_handle, bytes); // encoded bytes + let bytes = gob_encode( + big_float, + Accuracy::Exact, + Mode::ToNearestEven, + GOB_DEFAULT_PREC, + ); + println!("encoded bytes {:#?}", bytes); + self.m_types_lock().mb_set(m_buffer_handle, bytes.to_vec()); } -} -pub fn gob_encode(floating_number: f64) -> Vec { - let mut buf = Vec::new(); + // fn mb_from_big_float(&self, m_buffer_handle: RawHandle, big_float_handle: RawHandle) { + // let big_float = self.m_types_lock().bf_get_f64(big_float_handle); + // println!("big float is {:#?}", big_float.to_bits()); + + // let mut stream_ser = StreamSerializer::new_with_buffer(); + + // // serialize the f64 value using the obtained Serializer + // let _ = stream_ser.serialize_with_type_id(TypeId::FLOAT, &big_float); - // Encode the version + // // extract the serialized data + // let output_buffer = stream_ser.into_inner(); + // let bytes = output_buffer.collect(); + // println!("output buffer is {:#?}", bytes); + + // self.m_types_lock().mb_set(m_buffer_handle, bytes); + // } +} +fn gob_encode(f: f64, accuracy: Accuracy, mode: Mode, precision: u64) -> Vec { + let bits = f.to_bits(); + let (_sign, exp, mut mant) = extract_float_components(bits); + + let mut _n = 0; + let mut _form = Form::Zero; + if f.is_finite() { + let mant_number_of_bits = num_bits(mant) as u64; + _form = Form::Finite; + _n = (GOB_DEFAULT_PREC + (_W - 1)) / _W; + if mant_number_of_bits < _n { + _n = mant_number_of_bits; + } + } else { + _form = Form::Infinite; + } + + let mut buf = vec![]; buf.push(FLOAT_GOB_VERSION); - // Calculate the mode byte - let mut mode_byte = 0u8; - // Extract rounding mode bits (lowest 3 bits) - let rounding_mode_bits = ((floating_number.to_bits() >> 61) & 0b111) as u8; // Extract 3 bits - mode_byte |= rounding_mode_bits << 5; + let mode_bits = mode as u8 & 0b111; + let acc_bits = (accuracy as u8 + 1) & 0b11; + let form_bits = _form as u8 & 0b11; + + let mut combined_byte = (mode_bits << 5) | (acc_bits << 3) | (form_bits << 1); + if f.is_sign_negative() { + combined_byte |= 1; + } - // Extract accuracy bits (next 2 bits) - let accuracy_bits = ((floating_number.to_bits() >> 59) & 0b11) as u8; // Extract 2 bits - mode_byte |= accuracy_bits << 3; + buf.push(combined_byte); + buf.extend_from_slice(&precision.to_be_bytes()); - // Extract form bits (next 2 bits) - let form_bits = ((floating_number.to_bits() >> 57) & 0b11) as u8; // Extract 2 bits - mode_byte |= form_bits << 1; + if f.is_finite() { + buf.extend_from_slice(&exp.to_be_bytes()); + mant >>= 64 - _n; + buf.extend_from_slice(&mant.to_be_bytes()) + } - // Extract negation flag (highest bit) - let negation_flag_bit = ((floating_number.to_bits() >> 63) & 0b1) as u8; // Extract 1 bit - mode_byte |= negation_flag_bit; + buf +} - // Push the mode byte to the buffer - buf.push(mode_byte); +fn extract_float_components(bits: u64) -> (u64, i64, u64) { + // Define masks for sign, exponent, and mantissa + let sign_mask: u64 = 0x8000000000000000; + let exponent_mask: u64 = 0x7FF0000000000000; + let mantissa_mask: u64 = 0x000FFFFFFFFFFFFF; - // Encode precision - let precision_bytes = floating_number.to_bits().to_be_bytes(); - buf.extend_from_slice(&precision_bytes); + // Extract sign + let sign = (bits & sign_mask) >> 63; - // Encode exponent - let exponent_bytes = floating_number.to_bits().to_be_bytes(); - buf.extend_from_slice(&exponent_bytes[7..]); + // Extract exponent + let mut exponent = ((bits & exponent_mask) >> 52) as i64; + // Adjust for bias + exponent -= 1023; - // Calculate the number of mantissa words - let n = (((floating_number.to_bits() >> 52) & 0x7FF) as usize) + (_W - 1) / _W; + // Extract mantissa + let mut mantissa = bits & mantissa_mask; - // Encode mantissa - if form_bits == 0b11 { - let mantissa_bytes = floating_number.to_bits().to_be_bytes(); - buf.extend_from_slice(&mantissa_bytes[8..8 + n]); + // Add implicit leading bit + mantissa |= 0x0010000000000000; + + (sign, exponent, mantissa) +} + +fn num_bits(mut n: u64) -> usize { + if n == 0 { + return 1; // Special case for 0 } - buf + let mut bits = 0; + while n > 0 { + bits += 1; + n >>= 1; // Shift right to check the next bit + } + bits }