ZettaScaleLabs / stabby

A Stable ABI for Rust with compact sum-types
Other
328 stars 13 forks source link

Support for libffi #59

Closed segeljakt closed 7 months ago

segeljakt commented 7 months ago

I'm trying to use stabby in an interpreter for a programming language to dynamically load libraries whose functions can have complex signatures that are not known until interpretation-time. To load the libraries, I am using libloading and libffi:

For example, if I have this library:

#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
    a + b
}

I can load it dynamically and run it with:

use libloading::Library;

use libffi::middle::Arg;
use libffi::middle::Builder;
use libffi::middle::Cif;
use libffi::middle::CodePtr;
use libffi::middle::Type;

#[test]
fn test_i32_add() {
    unsafe {
        let lib = Library::new("liblibrary.dylib").unwrap();
        let fun = lib.get("add".as_bytes()).unwrap();
        let res = Builder::new()
            .args([Type::i32(), Type::i32()])
            .res(Type::i32())
            .into_cif()
            .call::<i32>(CodePtr(*fun), &[Arg::new(&10), Arg::new(&20)]);
        assert_eq!(res, 30);
    }
}

I would like to do the same thing as above, but for more complex types like String, Vec<T>, Rc<T>, which is what brought me to stabby. I think I can already use stabby for this purpose, but a challenge is that I would need to construct Types that correctly represent the layout of each stabby type. I think this is an ok solution, but it might get difficult if the ABI changes. If it aligns with stabby's goals, would it be possible to extend stabby with a feature that provides libffi Type information for each stabby type?

For example, given a library:

use stabby::string::String;
#[no_mangle]
pub extern "C" fn add(a: String, b: String) -> String {
    a.concat(b)
}

This could be an imaginary API:

use stabby::string::String;

use libloading::Library;

use libffi::middle::Arg;
use libffi::middle::Builder;
use libffi::middle::Cif;
use libffi::middle::CodePtr;
use libffi::middle::Type;

#[test]
fn test_string_add() {
    unsafe {
        let lib = Library::new("liblibrary.dylib").unwrap();
        let fun = lib.get("add".as_bytes()).unwrap();
        let res = Builder::new()
            .args([String::libffi_type(), String::libffi_type()]) // <--- here
            .res(String::libffi_type()) // <--- here
            .into_cif()
            .call::<i32>(CodePtr(*fun), &[Arg::new(&10), Arg::new(&20)]);
        assert_eq!(res, 30);
    }
}

Thanks for the great crate.

segeljakt commented 7 months ago

Oops, I think I found a workaround. It is possible to achieve what I need using:

#[test]
fn test_string_concat() {
    let lib = load();
    let fun = fun(&lib, "concat");
    unsafe {
        let layout = Layout::new::<StabbyString>();
        let ptr = alloc(layout);
        // Create string type
        let string_ty = &mut libffi::low::ffi_type {
            size: layout.size(),
            alignment: layout.align() as u16,
            type_: libffi::raw::FFI_TYPE_STRUCT as u16,
            elements: [Type::structure([]).as_raw_ptr()].as_mut_ptr(),
        } as *mut ffi_type;

        let mut arg_types = [string_ty, string_ty];
        let nargs = arg_types.len() as u32;
        let mut cif = ffi_cif::default();
        ffi_prep_cif(
            &mut cif,
            ffi_abi_FFI_DEFAULT_ABI,
            nargs,
            string_ty,
            arg_types.as_mut_ptr(),
        );
        let a0 = StabbyString::from("hello");
        let a1 = StabbyString::from("world");
        ffi_call(
            &mut cif,
            Some(*CodePtr(*fun).as_safe_fun()),
            ptr as *mut c_void,
            [Arg::new(&a0), Arg::new(&a1)].as_ptr() as *mut *mut c_void,
        );
        std::mem::forget(a0);
        std::mem::forget(a1);
        let s = ptr.cast::<StabbyString>().read();
        assert_eq!(s, "helloworld");
    };
}
p-avital commented 7 months ago

Hi there,

I'm currently on holiday with little computer access, so I can't make very detailed answers, but I'm sure integration would be possible (although it'd require a bit of effort).

Very happy that you found a workaround that suits you :)

segeljakt commented 7 months ago

Thanks, no problem. Hope you have a great holiday :)