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.

I added ffi buffer versions of all generated scaffolding functions.    I
didn't make them for "static" functions, like the rust_buffer FFI
functions.  I don't think we need them there since the signature is
always the same.
  • Loading branch information
bendk committed Mar 18, 2024
1 parent b9b0dd1 commit 9d6063a
Show file tree
Hide file tree
Showing 22 changed files with 566 additions and 15 deletions.
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
3 changes: 2 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 Down
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
55 changes: 55 additions & 0 deletions fixtures/coverall/src/ffi_buffer_scaffolding_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/* 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::{uniffi_ffibuffer_uniffi_coverall_fn_func_divide_by_text, ComplexError};
use uniffi::{ffi_buffer_size, 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() {
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)
);
}

// 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 = [0_u64; ffi_buffer_size!(RustBuffer, RustCallStatus)];
let mut return_ffi_buffer = [0_u64; ffi_buffer_size!(RustBuffer)];
// 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);
// Create a RustCallStatus that the scaffolding function will write to
let mut rust_call_status = RustCallStatus::default();
// 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);
<&mut RustCallStatus as FfiSerialize>::write(args_cursor, &mut rust_call_status);
// Call the ffi-buffer version of the scaffolding function
unsafe {
uniffi_ffibuffer_uniffi_coverall_fn_func_divide_by_text(
args_ffi_buffer.as_mut_ptr(),
return_ffi_buffer.as_mut_ptr(),
);
}
// Deserialize the return from the return buffer
let return_value = <f32 as FfiSerialize>::get(&return_ffi_buffer);
// Lift the return from the deserialized value.
<Result<f32, ComplexError> as LiftReturn<crate::UniFfiTag>>::lift_foreign_return(
return_value,
rust_call_status,
)
}
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,14 +15,15 @@ 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"
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"]
Loading

0 comments on commit 9d6063a

Please sign in to comment.