Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding generator #27

Closed
wants to merge 18 commits into from
Closed

Binding generator #27

wants to merge 18 commits into from

Conversation

AlixANNERAUD
Copy link
Contributor

@AlixANNERAUD AlixANNERAUD commented Apr 21, 2024

Hello, in order to easily interface WAMR with Rust, I wrote "wamr-bindgen" macros that automatically write the necessary code (signature, binding function, conversions). At the moment, it is only a crude prototype, but it is functional in the case of simple types and references on composite types (generic types and composite types passed by value/returned are not supported).
For the following standalone function :

#[function_bindgen]
fn test_function(_a: i32, _b: f32, _c: String, _d: &str, _e: i64, _f: u64, _g: i8) -> u8 {
    42
}

the generated code is :

const __WAMR_BINDGEN_TEST_FUNCTION_SIGNATURE: &'static str = "(if$$IIi)i";
#[no_mangle]
pub unsafe extern "C" fn __wamr_bindgen_test_function(
    __wamr_environment: wamr_sys::wasm_exec_env_t,
    _a: i32,
    _b: f32,
    _c: u32,
    _d: u32,
    _e: i64,
    _f: i64,
    _g: i32,
) -> i32 {
    let __wamr_instance = unsafe {
        wamr_sys::wasm_runtime_get_module_inst(__wamr_environment)
    };
    let _a = _a as i32;
    if !unsafe { wamr_sys::wasm_runtime_validate_app_str_addr(__wamr_instance, _c) } {
        {
            ::core::panicking::panic_fmt(format_args!("Invalid pointer"));
        }
    }
    let _c: *const char = unsafe {
        std::mem::transmute(
            wamr_sys::wasm_runtime_addr_app_to_native(__wamr_instance, _c),
        )
    };
    let _c = unsafe { std::ffi::CStr::from_ptr(_c as *const i8) }
        .to_str()
        .unwrap()
        .to_string();
    if !unsafe { wamr_sys::wasm_runtime_validate_app_str_addr(__wamr_instance, _d) } {
        {
            ::core::panicking::panic_fmt(format_args!("Invalid pointer"));
        }
    }
    let _d: *const char = unsafe {
        std::mem::transmute(
            wamr_sys::wasm_runtime_addr_app_to_native(__wamr_instance, _d),
        )
    };
    let _d = unsafe { std::ffi::CStr::from_ptr(_d as *const i8) }.to_str().unwrap();
    let _e = _e as i64;
    let _f = _f as u64;
    let _g = _g as i8;
    test_function(_a, _b, _c, _d, _e, _f, _g) as i32
}

and for an impl on a structure :

struct Test {}

#[impl_bindgen]
impl Test {
    fn test_function(
        &self,
        a: i32,
        b: f32,
        c: String,
        d: &str,
        e: i64,
        f: &u64,
        g: i8,
        h: u8,
        i: i16,
        j: u16,
        k: f64,
    ) -> u8 {
        42
    }
}

the generated code is :

const __WAMR_BINDGEN_TEST_FUNCTION_SIGNATURE: &'static str = "(*if$$I*iiiiF)i";
#[no_mangle]
pub unsafe extern "C" fn __wamr_bindgen_Test_test_function(
    __wamr_environment: wamr_sys::wasm_exec_env_t,
    __self: u32,
    a: i32,
    b: f32,
    c: u32,
    d: u32,
    e: i64,
    f: u32,
    g: i32,
    h: i32,
    i: i32,
    j: i32,
    k: f64,
) -> i32 {
    let __wamr_instance = unsafe {
        wamr_sys::wasm_runtime_get_module_inst(__wamr_environment)
    };
    if !unsafe {
        wamr_sys::wasm_runtime_validate_app_addr(
            __wamr_instance,
            __self,
            std::mem::size_of::<Test>() as u32,
        )
    } {
        {
            ::core::panicking::panic_fmt(format_args!("Invalid pointer"));
        }
    }
    let __self: &mut Test = unsafe {
        std::mem::transmute(
            wamr_sys::wasm_runtime_addr_app_to_native(__wamr_instance, __self),
        )
    };
    let a = a as i32;
    if !unsafe { wamr_sys::wasm_runtime_validate_app_str_addr(__wamr_instance, c) } {
        {
            ::core::panicking::panic_fmt(format_args!("Invalid pointer"));
        }
    }
    let c: *const char = unsafe {
        std::mem::transmute(
            wamr_sys::wasm_runtime_addr_app_to_native(__wamr_instance, c),
        )
    };
    let c = unsafe { std::ffi::CStr::from_ptr(c as *const i8) }
        .to_str()
        .unwrap()
        .to_string();
    if !unsafe { wamr_sys::wasm_runtime_validate_app_str_addr(__wamr_instance, d) } {
        {
            ::core::panicking::panic_fmt(format_args!("Invalid pointer"));
        }
    }
    let d: *const char = unsafe {
        std::mem::transmute(
            wamr_sys::wasm_runtime_addr_app_to_native(__wamr_instance, d),
        )
    };
    let d = unsafe { std::ffi::CStr::from_ptr(d as *const i8) }.to_str().unwrap();
    let e = e as i64;
    if !unsafe {
        wamr_sys::wasm_runtime_validate_app_addr(
            __wamr_instance,
            f,
            std::mem::size_of::<u64>() as u32,
        )
    } {
        {
            ::core::panicking::panic_fmt(format_args!("Invalid pointer"));
        }
    }
    let f: &u64 = unsafe {
        std::mem::transmute(
            wamr_sys::wasm_runtime_addr_app_to_native(__wamr_instance, f),
        )
    };
    let g = g as i8;
    let h = h as u8;
    let i = i as i16;
    let j = j as u16;
    Test::test_function(__self, a, b, c, d, e, f, g, h, i, j, k) as i32
}

Can I have any advice on that ?

src/runtime.rs Outdated Show resolved Hide resolved
src/lib.rs Outdated Show resolved Hide resolved
src/host_function.rs Outdated Show resolved Hide resolved
src/host_function.rs Outdated Show resolved Hide resolved
@lum1n0us
Copy link
Collaborator

Thanks for contribution.

I noticed the PR is using APIs of wamr_sys in generated code which is not our suggestions. We are hoping people use wamr-rust-sdk APIs to keep their code safe and don't involved those unsafe blocks.

@AlixANNERAUD AlixANNERAUD requested a review from wenyongh as a code owner April 22, 2024 08:47
@AlixANNERAUD AlixANNERAUD requested a review from lum1n0us April 22, 2024 12:02
@AlixANNERAUD
Copy link
Contributor Author

AlixANNERAUD commented Apr 22, 2024

Here is the output for both example with new abstraction :

#[no_mangle]
pub unsafe extern "C" fn __wamr_bindgen_test_function(
    __wamr_environment: wamr_sys::wasm_exec_env_t,
    _a: i32,
    _b: f32,
    _c: u32,
    _d: u32,
    _e: i64,
    _f: i64,
    _g: i32,
) -> i32 {
    let __wamr_environment = wamr_rust_sdk::execution_environment::ExecutionEnvironment::from(
        __wamr_environment,
    );
    let __wamr_instance = __wamr_environment.get_instance();
    let _a = _a as i32;
    let _c = __wamr_instance.app_to_native_str(_c).to_string();
    let _d = __wamr_instance.app_to_native_str(_d);
    let _e = _e as i64;
    let _f = _f as u64;
    let _g = _g as i8;
    test_function(_a, _b, _c, _d, _e, _f, _g) as i32
}

and :

#[no_mangle]
pub unsafe extern "C" fn __wamr_bindgen_Test_test_function(
    __wamr_environment: wamr_sys::wasm_exec_env_t,
    __self: u32,
    a: i32,
    b: f32,
    c: u32,
    d: u32,
    e: i64,
    f: u32,
    g: i32,
    h: i32,
    i: i32,
    j: i32,
    k: f64,
) -> i32 {
    let __wamr_environment = wamr_rust_sdk::execution_environment::ExecutionEnvironment::from(
        __wamr_environment,
    );
    let __wamr_instance = __wamr_environment.get_instance();
    let __self: &Test = __wamr_instance.app_to_native_ref(__self);
    let a = a as i32;
    let c = __wamr_instance.app_to_native_str(c).to_string();
    let d = __wamr_instance.app_to_native_str(d);
    let e = e as i64;
    let f: &u64 = __wamr_instance.app_to_native_ref(f);
    let g = g as i8;
    let h = h as u8;
    let i = i as i16;
    let j = j as u16;
    Test::test_function(__self, a, b, c, d, e, f, g, h, i, j, k) as i32
}

@lum1n0us
Copy link
Collaborator

  • __wamr_environment.get_instance(). we prefer user using instance.rs instead of wamr C structure.
  • app_to_native_str. we prefer user doesn't be aware of it.

@AlixANNERAUD
Copy link
Contributor Author

AlixANNERAUD commented May 13, 2024

  • __wamr_environment.get_instance(). we prefer user using instance.rs instead of wamr C structure.

  • app_to_native_str. we prefer user doesn't be aware of it.

  • I don't understand. I don't interact directly with the C instance structure; rather, it's encapsulated within the Rust instance structure. However, I'm constrained by the parameter type, which is intrinsic to WAMR native function signature.
  • Okay, but how would you suggest translating addresses from Wasm to native then?

@lum1n0us
Copy link
Collaborator

  • Okay, but how would you suggest translating addresses from Wasm to native then?

I am not quite following why we need to operate address like C in Rust World?

@AlixANNERAUD
Copy link
Contributor Author

I am not quite following why we need to operate address like C in Rust World?

How am I supposed to pass, for example, strings or arrays from WASM to the native and vice versa? (necessary for the serialization of complex structures).

@lum1n0us
Copy link
Collaborator

The whole idea is to keep user interfaces simple. We don't want to let APIs' users keep much concepts in their mind when programing with wamr-rust-sdk. Using a String as an example, it will be too much if they have to 1. move a string to wasm memory from host memory. 2. construct a WasmValue as a parameter of a wasm function. 3. most important thing, rust strings can be used directly because of encoding and \0.

In my mind, an ideal result is use one API, like WasmValue::String() only, to finish all previous tasks together.

@AlixANNERAUD
Copy link
Contributor Author

AlixANNERAUD commented May 21, 2024

The whole idea is to keep user interfaces simple. We don't want to let APIs' users keep much concepts in their mind when programing with wamr-rust-sdk. Using a String as an example, it will be too much if they have to 1. move a string to wasm memory from host memory. 2. construct a WasmValue as a parameter of a wasm function. 3. most important thing, rust strings can be used directly because of encoding and \0.

In my mind, an ideal result is use one API, like WasmValue::String() only, to finish all previous tasks together.

Ok but I'm struggling to see how you plan to implement WasmValue::String(). Besides wrapping it around a pointer returned by WASM to native, I don't quite see how it's possible without using address translation / raw pointers (I'm talking about the case where wasm calls a host function).

Furthermore, if you don't want to complicate the API for the end user, would it be possible to at least expose wamr-sys (pub use wamr-sys as sys;) to provide flexibility for users who desire it?

@lum1n0us
Copy link
Collaborator

without using address translation / raw pointers

Sorry for the mis-understand. address translation is still necessary in rust-sdk. But rust-sdk users shouldn't be awarded of that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants