Skip to content

Commit

Permalink
Added ffi-buffer scaffolding functions
Browse files Browse the repository at this point in the history
These are alternate versions of the current scaffolding functions that
use u64 buffers to handle their input/output.

The main use case is the gecko-js bindings used in Firefox.  This allows
us to replace the generated C++ code with static code, since the
scaffolding functions now always have the exact same signature.

This commit generates FFI buffer versions for scaffolding functions
defined for user functions/methods.  It does not generate them for
functions with known/static signatures like the rust buffer FFI
functions, or the object clone/delete functions.  If the signature is
always the same, then there isn't a problem calling the normal
scaffolding functions.
  • Loading branch information
bendk committed Apr 22, 2024
1 parent 44d4915 commit bc2793a
Show file tree
Hide file tree
Showing 22 changed files with 567 additions and 11 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion examples/arithmetic/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ uniffi = { workspace = true }
thiserror = "1.0"

[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] }

[dev-dependencies]
uniffi = { workspace = true, features = ["bindgen-tests"] }
3 changes: 2 additions & 1 deletion fixtures/callbacks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ crate-type = ["lib", "cdylib"]
name = "uniffi_fixture_callbacks"

[dependencies]
uniffi = { workspace = true }
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features=["scaffolding-ffi-buffer-fns"] }
thiserror = "1.0"

[build-dependencies]
Expand Down
4 changes: 3 additions & 1 deletion fixtures/coverall/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ crate-type = ["lib", "cdylib"]
name = "uniffi_coverall"

[dependencies]
uniffi = { workspace = true }
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features=["scaffolding-ffi-buffer-fns"]}
once_cell = "1.12"
thiserror = "1.0"

Expand All @@ -20,3 +21,4 @@ uniffi = { workspace = true, features = ["build"] }

[dev-dependencies]
uniffi = { workspace = true, features = ["bindgen-tests"] }
uniffi_meta = { path = "../../uniffi_meta/" }
4 changes: 4 additions & 0 deletions fixtures/coverall/src/coverall.udl
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,12 @@ namespace coverall {

void try_input_return_only_dict(ReturnOnlyDict d);

[Throws=ComplexError]
f32 divide_by_text(f32 value, string value_as_text);

Getters test_round_trip_through_rust(Getters getters);
void test_round_trip_through_foreign(Getters getters);

};

dictionary SimpleDict {
Expand Down
61 changes: 61 additions & 0 deletions fixtures/coverall/src/ffi_buffer_scaffolding_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use crate::ComplexError;
use uniffi::{
ffi_buffer_size, FfiBufferElement, FfiSerialize, LiftReturn, Lower, RustBuffer, RustCallStatus,
};

/// Test the FFI-buffer version of our scaffolding functions by manually calling one.
///
/// We use the `get_complex_error` version, since it's one of more complex cases:
/// - It inputs multiple arguments
/// - The Rust function returns a Result<> type, which means the ffi-buffer scaffolding function
/// needs to deserialize the `RustCallStatus` out pointer, pass it to the regular scaffolding
/// function, and everything needs to be put back together in the end.
#[test]
fn test_ffi_buffer_scaffolding() {
// Call the ffi-buffer version of the scaffolding function for `divide_by_text`
//
// This simulates the work that happens on the foreign side.
fn call_ffi_buffer_divide_by_text(
value: f32,
value_as_text: String,
) -> Result<f32, ComplexError> {
// Get buffers ready to store the arguments/return values
let mut args_ffi_buffer = [FfiBufferElement::default(); ffi_buffer_size!(f32, RustBuffer)];
let mut return_ffi_buffer =
[FfiBufferElement::default(); ffi_buffer_size!(f32, RustCallStatus)];
// Lower the arguments
let value_lowered = <f32 as Lower<crate::UniFfiTag>>::lower(value);
let value_as_text_lowered = <String as Lower<crate::UniFfiTag>>::lower(value_as_text);
// Serialize the lowered arguments plus the RustCallStatus into the argument buffer
let args_cursor = &mut args_ffi_buffer.as_mut_slice();
<f32 as FfiSerialize>::write(args_cursor, value_lowered);
<RustBuffer as FfiSerialize>::write(args_cursor, value_as_text_lowered);
// Call the ffi-buffer version of the scaffolding function
unsafe {
crate::uniffi_ffibuffer_uniffi_coverall_fn_func_divide_by_text(
args_ffi_buffer.as_mut_ptr(),
return_ffi_buffer.as_mut_ptr(),
);
}
// Deserialize the return and the RustCallStatus from the return buffer
let return_cursor = &mut return_ffi_buffer.as_slice();
let return_value = <f32 as FfiSerialize>::read(return_cursor);
let rust_call_status = <RustCallStatus as FfiSerialize>::read(return_cursor);
// Lift the return from the deserialized value.
<Result<f32, ComplexError> as LiftReturn<crate::UniFfiTag>>::lift_foreign_return(
return_value,
rust_call_status,
)
}

assert_eq!(call_ffi_buffer_divide_by_text(1.0, "2".into()), Ok(0.5));
assert_eq!(call_ffi_buffer_divide_by_text(5.0, "2.5".into()), Ok(2.0));
assert_eq!(
call_ffi_buffer_divide_by_text(1.0, "two".into()),
Err(ComplexError::UnknownError)
);
}
10 changes: 10 additions & 0 deletions fixtures/coverall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use std::time::SystemTime;

use once_cell::sync::Lazy;

#[cfg(test)]
mod ffi_buffer_scaffolding_test;

mod traits;
pub use traits::{
ancestor_names, get_string_util_traits, get_traits, make_rust_getters, test_getters,
Expand Down Expand Up @@ -232,6 +235,13 @@ fn try_input_return_only_dict(_d: ReturnOnlyDict) {
// FIXME: should be a compile-time error rather than a runtime error (#1850)
}

pub fn divide_by_text(value: f32, value_as_text: String) -> Result<f32, ComplexError> {
match value_as_text.parse::<f32>() {
Ok(divisor) if divisor != 0.0 => Ok(value / divisor),
_ => Err(ComplexError::UnknownError),
}
}

#[derive(Debug, Clone)]
pub struct DictWithDefaults {
name: String,
Expand Down
3 changes: 2 additions & 1 deletion fixtures/ext-types/lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ name = "uniffi_ext_types_lib"
[dependencies]
anyhow = "1"
bytes = "1.3"
uniffi = { workspace = true }
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"] }

uniffi-fixture-ext-types-external-crate = {path = "../external-crate"}
uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"}
Expand Down
5 changes: 3 additions & 2 deletions fixtures/futures/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ name = "uniffi-fixtures-futures"
path = "src/bin.rs"

[dependencies]
uniffi = { workspace = true, features = ["tokio", "cli"] }
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features = ["tokio", "cli", "scaffolding-ffi-buffer-fns"] }
async-trait = "0.1"
futures = "0.3"
thiserror = "1.0"
tokio = { version = "1.24.1", features = ["time", "sync"] }
once_cell = "1.18.0"

[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] }

[dev-dependencies]
uniffi = { workspace = true, features = ["bindgen-tests"] }
5 changes: 3 additions & 2 deletions fixtures/proc-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ name = "uniffi_proc_macro"
crate-type = ["lib", "cdylib"]

[dependencies]
uniffi = { workspace = true }
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"] }
thiserror = "1.0"
lazy_static = "1.4"

[build-dependencies]
uniffi = { workspace = true, features = ["build"] }
uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] }

[dev-dependencies]
uniffi = { workspace = true, features = ["bindgen-tests"] }
3 changes: 3 additions & 0 deletions uniffi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ bindgen-tests = [ "dep:uniffi_bindgen" ]
# Enable support for Tokio's futures.
# This must still be opted into on a per-function basis using `#[uniffi::export(async_runtime = "tokio")]`.
tokio = ["uniffi_core/tokio"]
# Generate extra scaffolding functions that use FfiBuffer to pass arguments and return values
# This is needed for the gecko-js bindings.
scaffolding-ffi-buffer-fns = ["uniffi_macros/scaffolding-ffi-buffer-fns"]
6 changes: 6 additions & 0 deletions uniffi_bindgen/src/interface/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ impl FfiFunction {
&self.name
}

/// Name of the FFI buffer version of this function that's generated when the
/// `scaffolding-ffi-buffer-fns` feature is enabled.
pub fn ffi_buffer_fn_name(&self) -> String {
uniffi_meta::ffi_buffer_symbol_name(&self.name)
}

pub fn is_async(&self) -> bool {
self.is_async
}
Expand Down
Loading

0 comments on commit bc2793a

Please sign in to comment.