Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Support u128/i128 in runtime interface #4703

Merged
merged 2 commits into from
Jan 22, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions primitives/runtime-interface/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,3 +471,56 @@ impl<T: sp_wasm_interface::PointerType> IntoFFIValue for Pointer<T> {
Ok(self.into())
}
}

/// Implement the traits for `u128`/`i128`
macro_rules! for_u128_i128 {
($type:ty) => {
/// `u128`/`i128` is passed as `u32`.
///
/// The `u32` is a pointer to an `[u8; 16]` array.
impl RIType for $type {
type FFIType = u32;
}

#[cfg(not(feature = "std"))]
impl IntoFFIValue for $type {
type Owned = ();

fn into_ffi_value(&self) -> WrappedFFIValue<u32> {
unsafe { (mem::transmute::<&Self, *const u8>(self) as u32).into() }
}
}

#[cfg(not(feature = "std"))]
impl FromFFIValue for $type {
fn from_ffi_value(arg: u32) -> $type {
unsafe { mem::transmute::<[u8; 16], $type>(<[u8; 16]>::from_ffi_value(arg)) }
}
}

#[cfg(feature = "std")]
impl FromFFIValue for $type {
type SelfInstance = $type;

fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<$type> {
let data = context.read_memory(Pointer::new(arg), 16)?;
let mut res = [0u8; 16];
res.copy_from_slice(&data);
Ok(unsafe { mem::transmute::<[u8; 16], $type>(res) })
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I wonder if this is safe? The alignment of u128 might be 16 and I am not sure sure if we can say anything regarding this about res.
  2. Does it assume that wasm and host have the same endianness?

I think this can be swapped with u128::from_le_bytes here instead of transmute?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. What? Why can we not say the same about res? It is as well 16 bytes. (Looking at the rust source code for from_le_bytes it does exactly the same transmute)
  2. Yes it assumes it. Please don't start again this xD As it is no primitive type, I probably also need to call to_le_bytes on the host?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But good catches! I searched yesterday evening for *_let_bytes, but failed for some reason 🤷‍♀️

}
}

#[cfg(feature = "std")]
impl IntoFFIValue for $type {
fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result<u32> {
let addr = context.allocate_memory(16)?;
let data = unsafe { mem::transmute::<$type, [u8; 16]>(self) };
context.write_memory(addr, &data)?;
Ok(addr.into())
}
}
}
}

for_u128_i128!(u128);
for_u128_i128!(i128);
3 changes: 3 additions & 0 deletions primitives/runtime-interface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@
//! | `u16` | `u16` | `Identity` |
//! | `u32` | `u32` | `Identity` |
//! | `u64` | `u64` | `Identity` |
//! | `i128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
//! | `i8` | `i8` | `Identity` |
//! | `i16` | `i16` | `Identity` |
//! | `i32` | `i32` | `Identity` |
//! | `i64` | `i64` | `Identity` |
//! | `u128` | `u32` | `v.as_ptr()` (pointer to a 16 byte array) |
//! | `bool` | `u8` | `if v { 1 } else { 0 }` |
//! | `&str` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
//! | `&[u8]` | `u64` | <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
Expand All @@ -93,6 +95,7 @@
//! | `&[T] where T: Encode` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 &#124; e.as_ptr() 32bit</code> |
//! | `[u8; N]` | `u32` | `v.as_ptr()` |
//! | `*const T` | `u32` | `Identity` |
//! | `Option<T>` | `u64` | `let e = v.encode();`<br><br><code>e.len() 32bit << 32 &#124; e.as_ptr() 32bit</code> |
//! | [`T where T: PassBy<PassBy=Inner>`](pass_by::Inner) | Depends on inner | Depends on inner |
//! | [`T where T: PassBy<PassBy=Codec>`](pass_by::Codec) | `u64`| <code>v.len() 32bit << 32 &#124; v.as_ptr() 32bit</code> |
//!
Expand Down
20 changes: 20 additions & 0 deletions primitives/runtime-interface/test-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,16 @@ pub trait TestApi {
fn overwrite_native_function_implementation() -> bool {
false
}

/// Gets an `u128` and returns this value
fn get_and_return_u128(val: u128) -> u128 {
val
}

/// Gets an `i128` and returns this value
fn get_and_return_i128(val: i128) -> i128 {
val
}
}

/// Two random external functions from the old runtime interface.
Expand Down Expand Up @@ -191,4 +201,14 @@ wasm_export_functions! {

assert!(test_api::overwrite_native_function_implementation());
}

fn test_u128_i128_as_parameter_and_return_value() {
for val in &[u128::max_value(), 1u128, 5000u128, u64::max_value() as u128] {
assert_eq!(*val, test_api::get_and_return_u128(*val));
}

for val in &[i128::max_value(), i128::min_value(), 1i128, 5000i128, u64::max_value() as i128] {
assert_eq!(*val, test_api::get_and_return_i128(*val));
}
}
}
5 changes: 5 additions & 0 deletions primitives/runtime-interface/test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ fn test_invalid_utf8_data_should_return_an_error() {
fn test_overwrite_native_function_implementation() {
call_wasm_method::<HostFunctions>("test_overwrite_native_function_implementation");
}

#[test]
fn test_u128_i128_as_parameter_and_return_value() {
call_wasm_method::<HostFunctions>("test_u128_i128_as_parameter_and_return_value");
}