Skip to content

Commit

Permalink
[feature] hyperledger#2622: u128/i128 support in FFI
Browse files Browse the repository at this point in the history
Signed-off-by: Shanin Roman <shanin1000@yandex.ru>
  • Loading branch information
Erigara authored and appetrosyan committed Jun 2, 2023
1 parent 33e52aa commit c9617e8
Show file tree
Hide file tree
Showing 4 changed files with 339 additions and 4 deletions.
Binary file modified configs/peer/validator.wasm
Binary file not shown.
2 changes: 1 addition & 1 deletion ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use repr_c::{
pub mod handle;
pub mod ir;
pub mod option;
mod primitives;
pub mod primitives;
pub mod repr_c;
pub mod slice;
mod std_impls;
Expand Down
162 changes: 159 additions & 3 deletions ffi/src/primitives.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
//! Logic related to the conversion of primitives to and from FFI-compatible representation

use crate::ffi_type;
use crate::{
ffi_type,
ir::Ir,
repr_c::{
read_non_local, write_non_local, COutPtr, COutPtrRead, COutPtrWrite, CType, CTypeConvert,
CWrapperType, Cloned, NonLocal,
},
FfiTuple2, ReprC, Result,
};

#[cfg(target_family = "wasm")]
mod wasm {
Expand Down Expand Up @@ -133,5 +141,153 @@ primitive_derive! { u32, i32, u64, i64 }
#[cfg(not(target_family = "wasm"))]
primitive_derive! { u8, i8, u16, i16 }

// TODO: u128/i128 is not FFI-safe. Must be properly serialized!
primitive_derive! { u128, i128 }
/// Ffi-safe representation of [`u128`]
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct FfiU128(FfiTuple2<u64, u64>);

// SAFETY: Transparent to `FfiTuple<u64, u64>` which is `ReprC`
unsafe impl ReprC for FfiU128 {}

/// Ffi-safe representation of [`i128`]
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct FfiI128(FfiTuple2<u64, u64>);

// SAFETY: Transparent to `FfiTuple<u64, u64>` which is `ReprC`
unsafe impl ReprC for FfiI128 {}

macro_rules! int128_derive {
($($src:ty => $dst:ty),+$(,)?) => {$(
impl Ir for $src {
type Type = Self;
}

impl CType<Self> for $src {
type ReprC = $dst;
}

impl CTypeConvert<'_, Self, $dst> for $src {
type RustStore = ();
type FfiStore = ();

fn into_repr_c(self, _: &mut Self::RustStore) -> $dst {
self.into()
}

// NOTE: calling this function should be safe since no pointers involved in conversion
unsafe fn try_from_repr_c(value: $dst, _: &mut Self::FfiStore) -> Result<Self> {
Ok(value.into())
}
}
impl Cloned for $src {}

// SAFETY: `u128/i128` doesn't use local store during conversion
unsafe impl NonLocal<Self> for $src {}

impl CWrapperType<Self> for $src {
type InputType = Self;
type ReturnType = Self;
}

impl COutPtr<Self> for $src {
type OutPtr = Self::ReprC;
}

impl COutPtrWrite<Self> for $src {
unsafe fn write_out(self, out_ptr: *mut Self::OutPtr) {
write_non_local::<_, Self>(self, out_ptr);
}
}

impl COutPtrRead<Self> for $src {
unsafe fn try_read_out(out_ptr: Self::OutPtr) -> Result<Self> {
read_non_local::<Self, Self>(out_ptr)
}
}
)*};
}

// Ffi-safe u128/i128 conversions
int128_derive! { u128 => FfiU128, i128 => FfiI128 }

impl From<u128> for FfiU128 {
#[allow(
clippy::cast_possible_truncation, // Truncation is done on purpose
clippy::arithmetic_side_effects
)]
#[inline]
fn from(value: u128) -> Self {
let lo = value as u64;
let hi = (value >> 64) as u64;
FfiU128(FfiTuple2(hi, lo))
}
}

impl From<FfiU128> for u128 {
#[allow(
clippy::cast_lossless,
clippy::cast_possible_truncation, // Truncation is done on purpose
clippy::arithmetic_side_effects
)]
#[inline]
fn from(FfiU128(FfiTuple2(hi, lo)): FfiU128) -> Self {
((hi as u128) << 64) | (lo as u128)
}
}

impl From<i128> for FfiI128 {
#[allow(clippy::cast_sign_loss)] // Intended behavior
fn from(value: i128) -> Self {
FfiI128(FfiU128::from(value as u128).0)
}
}

impl From<FfiI128> for i128 {
#[allow(clippy::cast_possible_wrap)] // Intended behavior
fn from(value: FfiI128) -> Self {
u128::from(FfiU128(value.0)) as i128
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn conversion_u128() {
let values = [
u128::MAX,
u128::from(u64::MAX),
u128::from(u32::MAX),
u128::from(u16::MAX),
u128::from(u8::MAX),
0,
];

for value in values {
assert_eq!(value, FfiU128::from(value).into())
}
}

#[test]
fn conversion_i128() {
let values = [
i128::MAX,
i128::from(i64::MAX),
i128::from(i32::MAX),
i128::from(i16::MAX),
i128::from(i8::MAX),
0,
i128::from(i8::MIN),
i128::from(i16::MIN),
i128::from(i32::MIN),
i128::from(i64::MIN),
i128::MIN,
];

for value in values {
assert_eq!(value, FfiI128::from(value).into())
}
}
}
179 changes: 179 additions & 0 deletions ffi/tests/ffi_export_import_u128_i128.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use iroha_ffi::{ffi_export, ffi_import};

macro_rules! derive_freestanding_export_import {
($(fn $ident:ident($inp:ty) -> $out:ty);+ $(;)?) => {
// FFI imports
$(
#[ffi_import]
pub fn $ident(value: $inp) -> $out {
unreachable!("replaced by ffi_import")
}
)*

// FFI exports
mod exports {
use std::alloc;
use super::*;

iroha_ffi::def_ffi_fns! { dealloc }

$(
#[ffi_export]
pub fn $ident(value: $inp) -> $out {
value
}
)*
}
};
}

derive_freestanding_export_import! {
fn freestanding_u128(u128) -> u128;
fn freestanding_i128(i128) -> i128;
fn freestanding_u128_ref(&u128) -> &u128;
fn freestanding_i128_ref(&i128) -> &i128;
fn freestanding_u128_slice(&[u128]) -> &[u128];
fn freestanding_i128_slice(&[i128]) -> &[i128];
fn freestanding_u128_vec(Vec<u128>) -> Vec<u128>;
fn freestanding_i128_vec(Vec<i128>) -> Vec<i128>;
fn freestanding_u128_box(Box<u128>) -> Box<u128>;
fn freestanding_i128_box(Box<i128>) -> Box<i128>;
fn freestanding_u128_array([u128; 6]) -> [u128; 6];
fn freestanding_i128_array([i128; 11]) -> [i128; 11];
}

fn u128_values() -> [u128; 6] {
[
u128::MAX,
u128::from(u64::MAX),
u128::from(u32::MAX),
u128::from(u16::MAX),
u128::from(u8::MAX),
0,
]
}

fn i128_values() -> [i128; 11] {
[
i128::MAX,
i128::from(i64::MAX),
i128::from(i32::MAX),
i128::from(i16::MAX),
i128::from(i8::MAX),
0,
i128::from(i8::MIN),
i128::from(i16::MIN),
i128::from(i32::MIN),
i128::from(i64::MIN),
i128::MIN,
]
}

#[test]
#[webassembly_test::webassembly_test]
fn u128_conversion() {
let values = u128_values();

for value in values {
assert_eq!(value, freestanding_u128(value));
}
}

#[test]
#[webassembly_test::webassembly_test]
fn i128_conversion() {
let values = i128_values();

for value in values {
assert_eq!(value, freestanding_i128(value));
}
}

#[test]
#[webassembly_test::webassembly_test]
fn u128_ref_conversion() {
let values = u128_values();

for value in values {
assert_eq!(value, *freestanding_u128_ref(&value));
}
}

#[test]
#[webassembly_test::webassembly_test]
fn i128_ref_conversion() {
let values = i128_values();

for value in values {
assert_eq!(value, *freestanding_i128_ref(&value));
}
}

#[test]
#[webassembly_test::webassembly_test]
fn u128_slice_conversion() {
let values = u128_values();

assert_eq!(values, *freestanding_u128_slice(&values));
}

#[test]
#[webassembly_test::webassembly_test]
fn i128_slice_conversion() {
let values = i128_values();

assert_eq!(values, *freestanding_i128_slice(&values));
}

#[test]
#[webassembly_test::webassembly_test]
fn u128_vec_conversion() {
let values = u128_values().to_vec();

assert_eq!(values, freestanding_u128_vec(values.clone()))
}

#[test]
#[webassembly_test::webassembly_test]
fn i128_vec_conversion() {
let values = i128_values().to_vec();

assert_eq!(values, freestanding_i128_vec(values.clone()))
}

#[test]
#[webassembly_test::webassembly_test]
fn u128_box_conversion() {
let values = u128_values();
for value in values {
let value = Box::new(value);
assert_eq!(value, freestanding_u128_box(value.clone()))
}
}

#[test]
#[webassembly_test::webassembly_test]
fn i128_box_conversion() {
let values = i128_values();

for value in values {
let value = Box::new(value);
assert_eq!(value, freestanding_i128_box(value.clone()))
}
}

#[test]
#[webassembly_test::webassembly_test]
fn u128_array_conversion() {
let values = u128_values();

assert_eq!(values, freestanding_u128_array(values))
}

#[test]
#[webassembly_test::webassembly_test]
fn i128_array_conversion() {
let values = i128_values();

assert_eq!(values, freestanding_i128_array(values))
}

0 comments on commit c9617e8

Please sign in to comment.