Skip to content

Commit

Permalink
Coverall2 and Rondpoint working with WASM! (#191)
Browse files Browse the repository at this point in the history
This PR adds:

- lowlevel structures
  - RustBuffers, 
  - Floats and 
  - c_void Void Pointers.
- high level mechanisms to do in a browser context:
- encoding and decoding strings, using `TextEncoder` and `TextDecoder`.
- tie Objects into the Garbage Collector with a `FinalizationRegistry`.

This is in service of getting the `rondpoint` fixture running.

Several things to note here:

### `error: rondpointpm::Optionneur is a private type`

Structs which derive the `uniffi::Object` currently need to have
visibility `pub`. If they're not, then errors like: `error:
rondpointpm::Optionneur is a private type` are shown.

This is due to `uniffi-rs` generating a public C ABI which uses the
private struct as part of its trait bounds. This feels like it's a Rust
bug (I hate saying that).

Next steps:

- File an issue: 
- `uniffi-rs` could generate a simpler ABI which didn't contain the
private type.
  - `uniffi-rs` could prevent non-`pub` structs
- Document: `uniffi-bindgen-react-native` should add this to a
trouble-shooting page.

### Additing a procmacro version of rondpoint and coverall2

This is due to mozilla/uniffi-rs#2329 not yet
being release.

Since we have now released to CocoaPods and npm, I can build against
uniffi-main. This is o k a y for a little project like this, but I do
worry about that a chem-spill requiring a rapid fix and release may get
messy.

### Default attribute values not seeming to work in proc-macro
`uniffi::Record`

I don't seem to be able to get default attributes working for records
going: as in the typescript isn't getting generated. If this is a bug,
this is almost certainly a uniffi-rs bug. Will file.
  • Loading branch information
jhugman authored Dec 27, 2024
1 parent c4c835f commit 2449029
Show file tree
Hide file tree
Showing 19 changed files with 926 additions and 98 deletions.
5 changes: 5 additions & 0 deletions crates/ubrn_bindgen/src/bindings/extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,11 @@ 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
100 changes: 57 additions & 43 deletions crates/ubrn_bindgen/src/bindings/gen_rust/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ use uniffi_bindgen::{
};

use crate::{
bindings::{extensions::ComponentInterfaceExt, metadata::ModuleMetadata},
bindings::{
extensions::{ComponentInterfaceExt, FfiFunctionExt},
metadata::ModuleMetadata,
},
switches::SwitchArgs,
AbiFlavor,
};
Expand Down Expand Up @@ -157,20 +160,25 @@ impl<'a> ComponentTemplate<'a> {
}

fn runtime_ident(&self) -> Ident {
ident("__runtime")
ident("f")
}

fn module_ident(&self) -> Ident {
ident("__module")
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 uniffi_alias_ident = self.uniffi_ident();
quote! {
use #runtime_ident::{self as #runtime_alias_ident, IntoRust};
use #runtime_ident::{self as #runtime_alias_ident, uniffi as #uniffi_alias_ident, IntoRust};
use #namespace_ident as #module_ident;
}
}
Expand All @@ -187,6 +195,8 @@ impl<'a> ComponentTemplate<'a> {
fn ffi_function(&mut self, func: &FfiFunction) -> TokenStream {
let module = self.module_ident();
let runtime = self.runtime_ident();
let uniffi = self.uniffi_ident();

let annotation = quote! { #[#runtime::export] };
let func_ident = ident(func.name());
let foreign_func_ident = self.flavor.foreign_ident(func.name());
Expand All @@ -206,9 +216,9 @@ impl<'a> ComponentTemplate<'a> {

let needs_call_status = func.has_rust_call_status_arg();
if needs_call_status {
let rust_status_ident = ident("__rust_call_status");
let foreign_status_ident = ident("__call_status");
let return_ident = ident("__return");
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 {
Expand All @@ -224,11 +234,16 @@ impl<'a> ComponentTemplate<'a> {
} else {
quote! {}
};
let unsafe_ = if func.is_unsafe() {
quote! { unsafe }
} else {
quote! {}
};

quote! {
#annotation
pub fn #foreign_func_ident(#args_decl #foreign_status_ident: &mut #runtime::CallStatus) #decl_suffix {
let mut #rust_status_ident = #runtime::RustCallStatus::default();
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;
#foreign_status_ident.copy_into(#rust_status_ident);
#return_value
Expand Down Expand Up @@ -290,18 +305,19 @@ impl<'a> ComponentTemplate<'a> {
FfiType::Float32 => quote! { #runtime::Float32 },
FfiType::Float64 => quote! { #runtime::Float64 },
FfiType::RustArcPtr(_) => quote! { #runtime::VoidPointer },
FfiType::RustBuffer(_external_ffi_metadata) => quote! { #runtime::ForeignBytes },
FfiType::ForeignBytes => quote! { #module::ForeignBytes },
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! { #module::RustCallStatus },
FfiType::RustCallStatus => quote! { #runtime::RustCallStatus },
FfiType::Reference(_ffi_type) => todo!(),
FfiType::VoidPointer => quote! { #runtime::VoidPointer },
}
}

fn ffi_type_rust(&self, t: &FfiType) -> TokenStream {
let uniffi = self.uniffi_ident();
match t {
FfiType::UInt8 => quote! { u8 },
FfiType::Int8 => quote! { i8 },
Expand All @@ -313,6 +329,8 @@ impl<'a> ComponentTemplate<'a> {
FfiType::Int64 => quote! { i64 },
FfiType::Float32 => quote! { f32 },
FfiType::Float64 => quote! { f64 },
FfiType::RustBuffer(_) => quote! { #uniffi::RustBuffer },
FfiType::RustArcPtr(_) => quote! { #uniffi::VoidPointer },
_ => todo!(),
}
}
Expand Down Expand Up @@ -407,12 +425,12 @@ mod unit_tests {
string.trim(),
trim_indent(
"
#[__runtime::export]
pub fn ubrn_my_function(__call_status: &mut __runtime::CallStatus) -> __runtime::Int8 {
let mut __rust_call_status = __runtime::RustCallStatus::default();
let __return = __module::my_function(&mut __rust_call_status).into_js();
__call_status.copy_into(__rust_call_status);
__return
#[f::export]
pub fn ubrn_my_function(f_status_: &mut f::RustCallStatus) -> f::Int8 {
let mut u_status_ = u::RustCallStatus::default();
let value_ = r::my_function(&mut u_status_).into_js();
f_status_.copy_into(u_status_);
value_
}
"
)
Expand All @@ -435,16 +453,12 @@ mod unit_tests {
string.trim(),
trim_indent(
"
#[__runtime::export]
pub fn ubrn_my_function(
num: __runtime::Int32,
__call_status: &mut __runtime::CallStatus,
) -> __runtime::Int8 {
let mut __rust_call_status = __runtime::RustCallStatus::default();
let __return = __module::my_function(i32::into_rust(num), &mut __rust_call_status)
.into_js();
__call_status.copy_into(__rust_call_status);
__return
#[f::export]
pub fn ubrn_my_function(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();
f_status_.copy_into(u_status_);
value_
}
"
)
Expand All @@ -467,21 +481,21 @@ mod unit_tests {
string.trim(),
trim_indent(
"
#[__runtime::export]
#[f::export]
pub fn ubrn_my_function(
left: __runtime::Int32,
right: __runtime::Float32,
__call_status: &mut __runtime::CallStatus,
) -> __runtime::Int8 {
let mut __rust_call_status = __runtime::RustCallStatus::default();
let __return = __module::my_function(
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(
i32::into_rust(left),
f32::into_rust(right),
&mut __rust_call_status,
&mut u_status_,
)
.into_js();
__call_status.copy_into(__rust_call_status);
__return
f_status_.copy_into(u_status_);
value_
}"
)
);
Expand All @@ -499,11 +513,11 @@ mod unit_tests {
string.trim(),
trim_indent(
"
#[__runtime::export]
pub fn ubrn_my_function(__call_status: &mut __runtime::CallStatus) {
let mut __rust_call_status = __runtime::RustCallStatus::default();
__module::my_function(&mut __rust_call_status);
__call_status.copy_into(__rust_call_status);
#[f::export]
pub fn ubrn_my_function(f_status_: &mut f::RustCallStatus) {
let mut u_status_ = u::RustCallStatus::default();
r::my_function(&mut u_status_);
f_status_.copy_into(u_status_);
}
"
)
Expand Down
8 changes: 8 additions & 0 deletions crates/ubrn_bindgen/src/bindings/gen_typescript/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ impl TsFlavorParams<'_> {
pub(crate) fn is_jsi(&self) -> bool {
matches!(self.inner, &AbiFlavor::Jsi)
}

pub(crate) fn supports_text_encoder(&self) -> bool {
!matches!(self.inner, &AbiFlavor::Jsi)
}

pub(crate) fn supports_finalization_registry(&self) -> bool {
!matches!(self.inner, &AbiFlavor::Jsi)
}
}

#[derive(Template)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
{{- self.import_infra_type("UniffiByteArray", "ffi-types")}}
{{- self.import_infra("UniffiRustCaller", "rust-call")}}
{{- self.import_infra_type("UniffiRustCallStatus", "rust-call")}}
{{- self.import_infra("RustBuffer", "ffi-types")}}

{%- let vtable_methods = cbi.vtable_methods() %}
{%- let trait_impl = format!("uniffiCallbackInterface{}", name) %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ export class {{ impl_class_name }} extends UniffiAbstractObject implements {{ pr
* {@inheritDoc uniffi-bindgen-react-native#UniffiAbstractObject.uniffiDestroy}
*/
uniffiDestroy(): void {
if ((this as any)[destructorGuardSymbol]) {
const ptr = (this as any)[destructorGuardSymbol];
if (ptr !== undefined) {
const pointer = {{ obj_factory }}.pointer(this);
{{ obj_factory }}.freePointer(pointer);
this[destructorGuardSymbol].markDestroyed();
{{ obj_factory }}.unbless(ptr);
delete (this as any)[destructorGuardSymbol];
}
}
Expand Down Expand Up @@ -148,6 +149,24 @@ const {{ obj_factory }}: UniffiObjectFactory<{{ type_name }}> = {
return instance;
},

{% if flavor.supports_finalization_registry() %}
registry: new FinalizationRegistry<UnsafeMutableRawPointer>((heldValue) => {
{{ obj_factory }}.freePointer(heldValue);
}),

bless(p: UnsafeMutableRawPointer): UniffiRustArcPtr {
const ptr = {
p, // make sure this object doesn't get optimized away.
markDestroyed: () => undefined,
};
{{ obj_factory }}.registry.register(ptr, p, ptr);
return ptr;
},

unbless(ptr: UniffiRustArcPtr) {
{{ obj_factory }}.registry.unregister(ptr);
},
{%- else %}
bless(p: UnsafeMutableRawPointer): UniffiRustArcPtr {
return uniffiCaller.rustCall(
/*caller:*/ (status) =>
Expand All @@ -156,6 +175,11 @@ const {{ obj_factory }}: UniffiObjectFactory<{{ type_name }}> = {
);
},

unbless(ptr: UniffiRustArcPtr) {
ptr.markDestroyed();
},
{%- endif %}

pointer(obj: {{ type_name }}): UnsafeMutableRawPointer {
if ((obj as any)[destructorGuardSymbol] === undefined) {
throw new UniffiInternalError.UnexpectedNullPointer();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
{{- self.import_infra("RustBuffer", "ffi-types") }}
{{- self.import_infra("uniffiCreateRecord", "records") }}

{%- let rec = ci|get_record_definition(name) %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,42 +1,24 @@
{{- self.import_infra("RustBuffer", "ffi-types") }}
{{- self.import_infra_type("UniffiByteArray", "ffi-types") }}
{{- self.import_infra("FfiConverterInt32", "ffi-converters") }}
{{- self.import_infra_type("FfiConverter", "ffi-converters") }}
{{- self.import_infra("uniffiCreateFfiConverterString", "ffi-converters") }}

const stringToArrayBuffer = (s: string): UniffiByteArray =>
uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_arraybuffer()) %}(s, status));

const arrayBufferToString = (ab: UniffiByteArray): string =>
uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_arraybuffer_to_string()) %}(ab, status));

const stringByteLength = (s: string): number =>
uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_bytelength()) %}(s, status));

const FfiConverterString = (() => {
const lengthConverter = FfiConverterInt32;
type TypeName = string;
class FFIConverter implements FfiConverter<UniffiByteArray, TypeName> {
lift(value: UniffiByteArray): TypeName {
return arrayBufferToString(value);
}
lower(value: TypeName): UniffiByteArray {
return stringToArrayBuffer(value);
}
read(from: RustBuffer): TypeName {
const length = lengthConverter.read(from);
const bytes = from.readBytes(length);
return arrayBufferToString(new Uint8Array(bytes));
}
write(value: TypeName, into: RustBuffer): void {
const buffer = stringToArrayBuffer(value).buffer;
const numBytes = buffer.byteLength;
lengthConverter.write(numBytes, into);
into.writeBytes(buffer);
}
allocationSize(value: TypeName): number {
return lengthConverter.allocationSize(0) + stringByteLength(value);
}
}

return new FFIConverter();
{%- if flavor.supports_text_encoder() %}
const stringConverter = (() => {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
return {
stringToBytes: (s: string) => encoder.encode(s),
bytesToString: (ab: UniffiByteArray) => decoder.decode(ab),
stringByteLength: (s: string) => encoder.encode(s).byteLength,
};
})();
{%- else %}
const stringConverter = {
stringToBytes: (s: string) =>
uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_arraybuffer()) %}(s, status)),
bytesToString: (ab: UniffiByteArray) =>
uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_arraybuffer_to_string()) %}(ab, status)),
stringByteLength: (s: string) =>
uniffiCaller.rustCall((status) => {% call ts::fn_handle(ci.ffi_function_string_to_bytelength()) %}(s, status)),
};
{%- endif %}
const FfiConverterString = uniffiCreateFfiConverterString(stringConverter);
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{%- import "macros.ts" as ts %}
{{- self.import_infra("RustBuffer", "ffi-types") }}
{{- self.import_infra("UniffiInternalError", "errors") -}}
{{- self.import_infra("UniffiRustCaller", "rust-call") }}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const {
const uniffiCaller = new UniffiRustCaller();
{%- else %}
const nativeModule = () => wasmBundle;
const uniffiCaller = new UniffiRustCaller(() => new wasmBundle.CallStatus());
const uniffiCaller = new UniffiRustCaller(() => new wasmBundle.RustCallStatus());
{%- endif %}

const uniffiIsDebug =
Expand Down
Loading

0 comments on commit 2449029

Please sign in to comment.