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

Use the C-ABI to call into Rust crates for WASM! #202

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions crates/ubrn_bindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ wasm = ["quote", "prettyplease", "syn", "proc-macro2"]
anyhow = { workspace = true }
askama = { workspace = true }
camino = { workspace = true }
cargo_metadata = { workspace = true }
clap = { workspace = true }
heck = { workspace = true }
paste = { workspace = true }
Expand Down
5 changes: 0 additions & 5 deletions crates/ubrn_bindgen/src/bindings/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,11 +300,6 @@ pub(crate) impl FfiFunction {
let name = self.name();
name.contains("ffi__") && name.contains("_internal_")
}

fn is_unsafe(&self) -> bool {
let name = self.name();
name.contains("_fn_clone_") || name.contains("_fn_free_") || name.contains("_rustbuffer_")
}
}

#[ext]
Expand Down
172 changes: 111 additions & 61 deletions crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@ use uniffi_bindgen::{
};

use crate::{
bindings::{
extensions::{ComponentInterfaceExt, FfiFunctionExt},
metadata::ModuleMetadata,
},
bindings::{extensions::ComponentInterfaceExt, metadata::ModuleMetadata},
switches::SwitchArgs,
AbiFlavor,
};
Expand Down Expand Up @@ -85,6 +82,14 @@ impl FlavorParams<'_> {
}
}

fn if_or_default<T: Default>(flag: bool, then: T) -> T {
if flag {
then
} else {
Default::default()
}
}

fn ident(id: &str) -> Ident {
Ident::new(id, Span::call_site())
}
Expand Down Expand Up @@ -151,10 +156,19 @@ impl<'a> ComponentTemplate<'a> {
.map(|f| self.ffi_function(&f))
.collect();

let extern_c: TokenStream = ci
.iter_ffi_functions_js_to_rust()
.map(|f| self.ffi_function_decl_c_abi(&f))
.collect();

quote! {
// Generated by uniffi-bindgen-react-native
#prelude

extern "C" {
#extern_c
}

#definitions
}
}
Expand All @@ -163,23 +177,18 @@ impl<'a> ComponentTemplate<'a> {
ident("f")
}

fn module_ident(&self) -> Ident {
ident("r")
}

fn uniffi_ident(&self) -> Ident {
ident("u")
}

fn prelude(&self, ci: &ComponentInterface) -> TokenStream {
let runtime_alias_ident = self.runtime_ident();
let runtime_ident = self.flavor.runtime_module();
let namespace_ident = ident(ci.namespace());
let module_ident = self.module_ident();
let library_ident = ident(ci.crate_name());
let uniffi_alias_ident = self.uniffi_ident();
quote! {
use #runtime_ident::{self as #runtime_alias_ident, uniffi as #uniffi_alias_ident, IntoRust};
use #namespace_ident as #module_ident;
use #library_ident;
}
}

Expand All @@ -192,8 +201,34 @@ impl<'a> ComponentTemplate<'a> {
}
}

fn ffi_function_decl_c_abi(&mut self, func: &FfiFunction) -> TokenStream {
let uniffi = self.uniffi_ident();
let func_ident = ident(func.name());

let args = func.arguments();
let args_decl: TokenStream = args.iter().map(|arg| self.arg_decl_c_abi(arg)).collect();

let decl_suffix = if let Some(type_) = func.return_type() {
let return_type = self.ffi_type_rust(type_);
quote! { -> #return_type }
} else {
quote! {}
};

let needs_call_status = func.has_rust_call_status_arg();
let call_status = if needs_call_status {
let rust_status_ident = ident("u_status_");
quote! { #rust_status_ident: &mut #uniffi::RustCallStatus }
} else {
quote! {}
};

quote! {
fn #func_ident(#args_decl #call_status) #decl_suffix;
}
}

fn ffi_function(&mut self, func: &FfiFunction) -> TokenStream {
let module = self.module_ident();
let runtime = self.runtime_ident();
let uniffi = self.uniffi_ident();

Expand All @@ -214,51 +249,31 @@ impl<'a> ComponentTemplate<'a> {
quote! {}
};

let call_suffix = if_or_default(has_return, quote! { .into_js() });

let needs_call_status = func.has_rust_call_status_arg();
if needs_call_status {
let rust_status_ident = ident("u_status_");
let foreign_status_ident = ident("f_status_");
let return_ident = ident("value_");
let let_value = if has_return {
quote! { let #return_ident = }
} else {
quote! {}
};
let return_value = if has_return {
quote! { #return_ident }
} else {
quote! {}
};
let call_suffix = if has_return {
quote! { .into_js() }
} else {
quote! {}
};
let unsafe_ = if func.is_unsafe() {
quote! { unsafe }
} else {
quote! {}
};
let let_value = if_or_default(has_return, quote! { let #return_ident = });
let return_value = if_or_default(has_return, quote! { #return_ident });

quote! {
#annotation
pub #unsafe_ fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::RustCallStatus) #decl_suffix {
pub unsafe fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::RustCallStatus) #decl_suffix {
let mut #rust_status_ident = #uniffi::RustCallStatus::default();
#let_value #module::#func_ident(#args_call &mut #rust_status_ident) #call_suffix;
#let_value #func_ident(#args_call &mut #rust_status_ident) #call_suffix;
#foreign_status_ident.copy_into(#rust_status_ident);
#return_value
}
}
} else {
let call_suffix = if has_return {
quote! { .into_js() }
} else {
quote! { ; }
};
let semicolon = if_or_default(!has_return, quote! { ; });
quote! {
#annotation
pub fn #foreign_func_ident(#args_decl) #decl_suffix {
#module::#func_ident(#args_call) #call_suffix
pub unsafe fn #foreign_func_ident(#args_decl) #decl_suffix {
#func_ident(#args_call) #call_suffix #semicolon
}
}
}
Expand All @@ -280,6 +295,12 @@ impl<'a> ComponentTemplate<'a> {
quote! { #ident: #typ, }
}

fn arg_decl_c_abi(&self, arg: &FfiArgument) -> TokenStream {
let ident = self.arg_ident(arg);
let typ = self.ffi_type_rust(&arg.type_());
quote! { #ident: #typ, }
}

fn arg_to_rust(&self, arg: &FfiArgument) -> TokenStream {
let ident = self.arg_ident(arg);
let rust_type = self.ffi_type_rust(&arg.type_());
Expand All @@ -292,7 +313,6 @@ impl<'a> ComponentTemplate<'a> {

fn ffi_type_foreign(&self, t: &FfiType) -> TokenStream {
let runtime = self.runtime_ident();
let module = self.module_ident();
match t {
FfiType::UInt8 => quote! { #runtime::UInt8 },
FfiType::Int8 => quote! { #runtime::Int8 },
Expand All @@ -304,15 +324,13 @@ impl<'a> ComponentTemplate<'a> {
FfiType::Int64 => quote! { #runtime::Int64 },
FfiType::Float32 => quote! { #runtime::Float32 },
FfiType::Float64 => quote! { #runtime::Float64 },
FfiType::Handle => quote! { #runtime::Handle },
FfiType::ForeignBytes => quote! { #runtime::ForeignBytes },
FfiType::RustArcPtr(_) => quote! { #runtime::VoidPointer },
FfiType::RustBuffer(_) => quote! { #runtime::ForeignBytes },
FfiType::ForeignBytes => quote! { #runtime::ForeignBytes },
FfiType::Callback(_) => quote! { #module::Callback },
FfiType::Struct(_) => quote! { #module::Struct },
FfiType::Handle => quote! { #module::Handle },
FfiType::RustCallStatus => quote! { #runtime::RustCallStatus },
FfiType::Reference(_ffi_type) => todo!(),
FfiType::VoidPointer => quote! { #runtime::VoidPointer },
_ => unimplemented!("ffi_type_foreign for {t:?}"),
}
}

Expand All @@ -329,9 +347,13 @@ impl<'a> ComponentTemplate<'a> {
FfiType::Int64 => quote! { i64 },
FfiType::Float32 => quote! { f32 },
FfiType::Float64 => quote! { f64 },
FfiType::RustBuffer(_) => quote! { #uniffi::RustBuffer },
FfiType::Handle => quote! { u64 },
FfiType::ForeignBytes => quote! { #uniffi::RustBuffer },
FfiType::RustArcPtr(_) => quote! { #uniffi::VoidPointer },
_ => todo!(),
FfiType::RustBuffer(_) => quote! { #uniffi::RustBuffer },
FfiType::RustCallStatus => quote! { #uniffi::RustCallStatus },
FfiType::VoidPointer => quote! { #uniffi::VoidPointer },
_ => unimplemented!("ffi_type_rust: {t:?}"),
}
}
}
Expand Down Expand Up @@ -417,7 +439,7 @@ mod unit_tests {
#[test]
fn happy_path() -> Result<()> {
let mut subject = subject();
let input = func("my_function", no_args(), return_(FfiType::Int8));
let input = func("happy_path_func", no_args(), return_(FfiType::Int8));
let output = subject.ffi_function(&input);
let string = formatted(output, true)?;

Expand All @@ -426,15 +448,22 @@ mod unit_tests {
trim_indent(
"
#[f::export]
pub fn ubrn_my_function(f_status_: &mut f::RustCallStatus) -> f::Int8 {
pub unsafe fn ubrn_happy_path_func(f_status_: &mut f::RustCallStatus) -> f::Int8 {
let mut u_status_ = u::RustCallStatus::default();
let value_ = r::my_function(&mut u_status_).into_js();
let value_ = happy_path_func(&mut u_status_).into_js();
f_status_.copy_into(u_status_);
value_
}
"
)
);

let output = subject.ffi_function_decl_c_abi(&input);
let string = formatted(output, false)?;
assert_eq!(
string.trim(),
"fn happy_path_func (u_status_ : & mut u :: RustCallStatus) -> i8 ;"
);
Ok(())
}

Expand All @@ -443,7 +472,7 @@ mod unit_tests {
let mut subject = subject();

let input = func(
"my_function",
"one_arg_func",
vec![arg("num", FfiType::Int32)].into_iter(),
return_(FfiType::Int8),
);
Expand All @@ -454,15 +483,25 @@ mod unit_tests {
trim_indent(
"
#[f::export]
pub fn ubrn_my_function(num: f::Int32, f_status_: &mut f::RustCallStatus) -> f::Int8 {
pub unsafe fn ubrn_one_arg_func(
num: f::Int32,
f_status_: &mut f::RustCallStatus,
) -> f::Int8 {
let mut u_status_ = u::RustCallStatus::default();
let value_ = r::my_function(i32::into_rust(num), &mut u_status_).into_js();
let value_ = one_arg_func(i32::into_rust(num), &mut u_status_).into_js();
f_status_.copy_into(u_status_);
value_
}
"
)
);

let output = subject.ffi_function_decl_c_abi(&input);
let string = formatted(output, false)?;
assert_eq!(
string.trim(),
"fn one_arg_func (num : i32 , u_status_ : & mut u :: RustCallStatus) -> i8 ;"
);
Ok(())
}

Expand All @@ -471,7 +510,7 @@ mod unit_tests {
let mut subject = subject();

let input = func(
"my_function",
"two_arg_func",
vec![arg("left", FfiType::Int32), arg("right", FfiType::Float32)].into_iter(),
return_(FfiType::Int8),
);
Expand All @@ -482,13 +521,13 @@ mod unit_tests {
trim_indent(
"
#[f::export]
pub fn ubrn_my_function(
pub unsafe fn ubrn_two_arg_func(
left: f::Int32,
right: f::Float32,
f_status_: &mut f::RustCallStatus,
) -> f::Int8 {
let mut u_status_ = u::RustCallStatus::default();
let value_ = r::my_function(
let value_ = two_arg_func(
i32::into_rust(left),
f32::into_rust(right),
&mut u_status_,
Expand All @@ -499,29 +538,40 @@ mod unit_tests {
}"
)
);

let output = subject.ffi_function_decl_c_abi(&input);
let string = formatted(output, false)?;
assert_eq!(string.trim(), "fn two_arg_func (left : i32 , right : f32 , u_status_ : & mut u :: RustCallStatus) -> i8 ;");
Ok(())
}

#[test]
fn void_return() -> Result<()> {
let mut subject = subject();

let input = func("my_function", no_args(), void());
let input = func("void_return_func", no_args(), void());
let output = subject.ffi_function(&input);
let string = formatted(output, true)?;
assert_eq!(
string.trim(),
trim_indent(
"
#[f::export]
pub fn ubrn_my_function(f_status_: &mut f::RustCallStatus) {
pub unsafe fn ubrn_void_return_func(f_status_: &mut f::RustCallStatus) {
let mut u_status_ = u::RustCallStatus::default();
r::my_function(&mut u_status_);
void_return_func(&mut u_status_);
f_status_.copy_into(u_status_);
}
"
)
);

let output = subject.ffi_function_decl_c_abi(&input);
let string = formatted(output, false)?;
assert_eq!(
string.trim(),
"fn void_return_func (u_status_ : & mut u :: RustCallStatus) ;"
);
Ok(())
}
}
Loading
Loading