Skip to content

Commit 2dcd78a

Browse files
committed
Added ffi-buffer scaffolding functions
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.
1 parent fa0d0fd commit 2dcd78a

File tree

25 files changed

+636
-15
lines changed

25 files changed

+636
-15
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/arithmetic/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ uniffi = { workspace = true }
1515
thiserror = "1.0"
1616

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

2021
[dev-dependencies]
2122
uniffi = { workspace = true, features = ["bindgen-tests"] }

fixtures/callbacks/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ crate-type = ["lib", "cdylib"]
1111
name = "uniffi_fixture_callbacks"
1212

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

1718
[build-dependencies]

fixtures/coverall/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ crate-type = ["lib", "cdylib"]
1111
name = "uniffi_coverall"
1212

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

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

2122
[dev-dependencies]
2223
uniffi = { workspace = true, features = ["bindgen-tests"] }
24+
uniffi_meta = { path = "../../uniffi_meta/" }

fixtures/coverall/src/coverall.udl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ namespace coverall {
3030

3131
void try_input_return_only_dict(ReturnOnlyDict d);
3232

33+
[Throws=ComplexError]
34+
f32 divide_by_text(f32 value, string value_as_text);
35+
3336
Getters test_round_trip_through_rust(Getters getters);
3437
void test_round_trip_through_foreign(Getters getters);
38+
3539
};
3640

3741
dictionary SimpleDict {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4+
5+
use crate::ComplexError;
6+
use uniffi::{ffi_buffer_size, FfiSerialize, LiftReturn, Lower, RustBuffer, RustCallStatus};
7+
8+
/// Test the FFI-buffer version of our scaffolding functions by manually calling one.
9+
///
10+
/// We use the `get_complex_error` version, since it's one of more complex cases:
11+
/// - It inputs multiple arguments
12+
/// - The Rust function returns a Result<> type, which means the ffi-buffer scaffolding function
13+
/// needs to deserialize the `RustCallStatus` out pointer, pass it to the regular scaffolding
14+
/// function, and everything needs to be put back together in the end.
15+
#[test]
16+
fn test_ffi_buffer_scaffolding() {
17+
// Call the ffi-buffer version of the scaffolding function for `divide_by_text`
18+
//
19+
// This simulates the work that happens on the foreign side.
20+
fn call_ffi_buffer_divide_by_text(
21+
value: f32,
22+
value_as_text: String,
23+
) -> Result<f32, ComplexError> {
24+
// Get buffers ready to store the arguments/return values
25+
let mut args_ffi_buffer = [0_u64; ffi_buffer_size!(f32, RustBuffer)];
26+
let mut return_ffi_buffer = [0_u64; ffi_buffer_size!(f32, RustCallStatus)];
27+
// Lower the arguments
28+
let value_lowered = <f32 as Lower<crate::UniFfiTag>>::lower(value);
29+
let value_as_text_lowered = <String as Lower<crate::UniFfiTag>>::lower(value_as_text);
30+
// Serialize the lowered arguments plus the RustCallStatus into the argument buffer
31+
let args_cursor = &mut args_ffi_buffer.as_mut_slice();
32+
<f32 as FfiSerialize>::write(args_cursor, value_lowered);
33+
<RustBuffer as FfiSerialize>::write(args_cursor, value_as_text_lowered);
34+
// Call the ffi-buffer version of the scaffolding function
35+
unsafe {
36+
crate::uniffi_ffibuffer_uniffi_coverall_fn_func_divide_by_text(
37+
args_ffi_buffer.as_mut_ptr(),
38+
return_ffi_buffer.as_mut_ptr(),
39+
);
40+
}
41+
// Deserialize the return and the RustCallStatus from the return buffer
42+
let return_cursor = &mut return_ffi_buffer.as_slice();
43+
let return_value = <f32 as FfiSerialize>::read(return_cursor);
44+
let rust_call_status = <RustCallStatus as FfiSerialize>::read(return_cursor);
45+
// Lift the return from the deserialized value.
46+
<Result<f32, ComplexError> as LiftReturn<crate::UniFfiTag>>::lift_foreign_return(
47+
return_value,
48+
rust_call_status,
49+
)
50+
}
51+
52+
assert_eq!(call_ffi_buffer_divide_by_text(1.0, "2".into()), Ok(0.5));
53+
assert_eq!(call_ffi_buffer_divide_by_text(5.0, "2.5".into()), Ok(2.0));
54+
assert_eq!(
55+
call_ffi_buffer_divide_by_text(1.0, "two".into()),
56+
Err(ComplexError::UnknownError)
57+
);
58+
}
59+
60+
/// Test the FFI-buffer version for a function that does not need a RustCallStatus out param.
61+
#[test]
62+
fn test_ffi_buffer_scaffolding_without_rust_call_status() {
63+
// Use a checksum function for this test.
64+
//
65+
// Checksum functions don't input any arguments, but that's fine since the main thing we want
66+
// to test is the return handling. That's the main difference between this case and case where
67+
// scaffolding functions do have a RustCallStatus out param.
68+
69+
let mut args_ffi_buffer = [0_u64; 0];
70+
let mut return_ffi_buffer = [0_u64; ffi_buffer_size!(u16)];
71+
72+
unsafe {
73+
crate::uniffi_ffibuffer_uniffi_coverall_checksum_func_get_error_dict(
74+
args_ffi_buffer.as_mut_ptr(),
75+
return_ffi_buffer.as_mut_ptr(),
76+
);
77+
}
78+
// Deserialize the return from the return buffer
79+
let return_value = <u16 as FfiSerialize>::get(&return_ffi_buffer);
80+
let metadata =
81+
uniffi_meta::read_metadata(&crate::UNIFFI_META_UNIFFI_COVERALL_FUNC_GET_ERROR_DICT)
82+
.unwrap();
83+
if let uniffi_meta::Metadata::Func(fn_metadata) = metadata {
84+
assert_eq!(return_value, fn_metadata.checksum.unwrap());
85+
} else {
86+
panic!("Expected function metadata");
87+
}
88+
}

fixtures/coverall/src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ use std::time::SystemTime;
99

1010
use once_cell::sync::Lazy;
1111

12+
#[cfg(test)]
13+
mod ffi_buffer_scaffolding_test;
14+
1215
mod traits;
1316
pub use traits::{
1417
ancestor_names, get_string_util_traits, get_traits, make_rust_getters, test_getters,
@@ -232,6 +235,13 @@ fn try_input_return_only_dict(_d: ReturnOnlyDict) {
232235
// FIXME: should be a compile-time error rather than a runtime error (#1850)
233236
}
234237

238+
pub fn divide_by_text(value: f32, value_as_text: String) -> Result<f32, ComplexError> {
239+
match value_as_text.parse::<f32>() {
240+
Ok(divisor) if divisor != 0.0 => Ok(value / divisor),
241+
_ => Err(ComplexError::UnknownError),
242+
}
243+
}
244+
235245
#[derive(Debug, Clone)]
236246
pub struct DictWithDefaults {
237247
name: String,

fixtures/ext-types/lib/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ name = "uniffi_ext_types_lib"
2222
[dependencies]
2323
anyhow = "1"
2424
bytes = "1.3"
25-
uniffi = { workspace = true }
25+
# Add the "scaffolding-ffi-buffer-fns" feature to make sure things can build correctly
26+
uniffi = { workspace = true, features = ["scaffolding-ffi-buffer-fns"] }
2627

2728
uniffi-fixture-ext-types-external-crate = {path = "../external-crate"}
2829
uniffi-fixture-ext-types-lib-one = {path = "../uniffi-one"}

fixtures/futures/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@ name = "uniffi-fixtures-futures"
1515
path = "src/bin.rs"
1616

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

2526
[build-dependencies]
26-
uniffi = { workspace = true, features = ["build"] }
27+
uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] }
2728

2829
[dev-dependencies]
2930
uniffi = { workspace = true, features = ["bindgen-tests"] }

fixtures/proc-macro/Cargo.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ name = "uniffi_proc_macro"
1111
crate-type = ["lib", "cdylib"]
1212

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

1819
[build-dependencies]
19-
uniffi = { workspace = true, features = ["build"] }
20+
uniffi = { workspace = true, features = ["build", "scaffolding-ffi-buffer-fns"] }
2021

2122
[dev-dependencies]
2223
uniffi = { workspace = true, features = ["bindgen-tests"] }

uniffi/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ bindgen-tests = [ "dep:uniffi_bindgen" ]
4343
# Enable support for Tokio's futures.
4444
# This must still be opted into on a per-function basis using `#[uniffi::export(async_runtime = "tokio")]`.
4545
tokio = ["uniffi_core/tokio"]
46+
# Generate extra scaffolding functions that use FfiBuffer to pass arguments and return values
47+
# This is needed for the gecko-js bindings.
48+
scaffolding-ffi-buffer-fns = ["uniffi_macros/scaffolding-ffi-buffer-fns"]

uniffi_bindgen/src/interface/ffi.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ impl FfiFunction {
218218
&self.name
219219
}
220220

221+
/// Name of the FFI buffer version of this function that's generated when the
222+
/// `scaffolding-ffi-buffer-fns` feature is enabled.
223+
pub fn ffi_buffer_fn_name(&self) -> String {
224+
uniffi_meta::ffi_buffer_symbol_name(&self.name)
225+
}
226+
221227
pub fn is_async(&self) -> bool {
222228
self.is_async
223229
}

0 commit comments

Comments
 (0)