Skip to content

Commit ccc97b5

Browse files
authored
Kotlin: Switch to JNA direct mapping (#2229)
According to https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md direct mapping can improve performance substantially, maybe even that of custom JNI. Given we know all methods it's easy to switch it all over.
1 parent bb06cb3 commit ccc97b5

File tree

5 files changed

+52
-84
lines changed

5 files changed

+52
-84
lines changed

uniffi_bindgen/src/bindings/kotlin/gen_kotlin/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ impl<'a> KotlinWrapper<'a> {
319319
.iter_local_types()
320320
.map(|t| KotlinCodeOracle.find(t))
321321
.filter_map(|ct| ct.initialization_fn())
322-
.map(|fn_name| format!("{fn_name}(lib)"));
322+
.map(|fn_name| format!("{fn_name}(this)"));
323323

324324
// Also call global initialization function for any external type we use.
325325
// For example, we need to make sure that all callback interface vtables are registered
@@ -765,7 +765,7 @@ mod filters {
765765
) -> Result<String, askama::Error> {
766766
let ffi_func = callable.ffi_rust_future_poll(ci);
767767
Ok(format!(
768-
"{{ future, callback, continuation -> UniffiLib.INSTANCE.{ffi_func}(future, callback, continuation) }}"
768+
"{{ future, callback, continuation -> UniffiLib.{ffi_func}(future, callback, continuation) }}"
769769
))
770770
}
771771

@@ -774,7 +774,7 @@ mod filters {
774774
ci: &ComponentInterface,
775775
) -> Result<String, askama::Error> {
776776
let ffi_func = callable.ffi_rust_future_complete(ci);
777-
let call = format!("UniffiLib.INSTANCE.{ffi_func}(future, continuation)");
777+
let call = format!("UniffiLib.{ffi_func}(future, continuation)");
778778
// May need to convert the RustBuffer from our package to the RustBuffer of the external package
779779
let call = match callable.return_type() {
780780
Some(return_type) if ci.is_external(return_type) => {
@@ -797,9 +797,7 @@ mod filters {
797797
ci: &ComponentInterface,
798798
) -> Result<String, askama::Error> {
799799
let ffi_func = callable.ffi_rust_future_free(ci);
800-
Ok(format!(
801-
"{{ future -> UniffiLib.INSTANCE.{ffi_func}(future) }}"
802-
))
800+
Ok(format!("{{ future -> UniffiLib.{ffi_func}(future) }}"))
803801
}
804802

805803
/// Remove the "`" chars we put around function/variable names

uniffi_bindgen/src/bindings/kotlin/templates/NamespaceLibraryTemplate.kt

Lines changed: 41 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,6 @@ private fun findLibraryName(componentName: String): String {
77
return "{{ config.cdylib_name() }}"
88
}
99

10-
private inline fun <reified Lib : Library> loadIndirect(
11-
componentName: String
12-
): Lib {
13-
return Native.load<Lib>(findLibraryName(componentName), Lib::class.java)
14-
}
15-
1610
// Define FFI callback types
1711
{%- for def in ci.ffi_definitions() %}
1812
{%- match def %}
@@ -55,88 +49,61 @@ internal open class {{ ffi_struct.name()|ffi_struct_name }}(
5549
{%- endmatch %}
5650
{%- endfor %}
5751

58-
5952
{%- macro decl_kotlin_functions(func_list) -%}
6053
{% for func in func_list -%}
61-
fun {{ func.name() }}(
54+
external fun {{ func.name() }}(
6255
{%- call kt::arg_list_ffi_decl(func) %}
6356
): {% match func.return_type() %}{% when Some with (return_type) %}{{ return_type.borrow()|ffi_type_name_by_value(ci) }}{% when None %}Unit{% endmatch %}
6457
{% endfor %}
6558
{%- endmacro %}
6659

60+
// A JNA Library to expose the extern-C FFI definitions.
61+
// This is an implementation detail which will be called internally by the public API.
62+
6763
// For large crates we prevent `MethodTooLargeException` (see #2340)
68-
// N.B. the name of the extension is very misleading, since it is
69-
// rather `InterfaceTooLargeException`, caused by too many methods
64+
// N.B. the name of the extension is very misleading, since it is
65+
// rather `InterfaceTooLargeException`, caused by too many methods
7066
// in the interface for large crates.
7167
//
7268
// By splitting the otherwise huge interface into two parts
73-
// * UniffiLib
74-
// * IntegrityCheckingUniffiLib (this)
69+
// * UniffiLib (this)
70+
// * IntegrityCheckingUniffiLib
71+
// And all checksum methods are put into `IntegrityCheckingUniffiLib`
7572
// we allow for ~2x as many methods in the UniffiLib interface.
76-
//
77-
// The `ffi_uniffi_contract_version` method and all checksum methods are put
78-
// into `IntegrityCheckingUniffiLib` and these methods are called only once,
79-
// when the library is loaded.
80-
internal interface IntegrityCheckingUniffiLib : Library {
81-
// Integrity check functions only
82-
{# newline below wanted #}
83-
84-
{%- call decl_kotlin_functions(ci.iter_ffi_function_integrity_checks()) %}
85-
}
86-
87-
// A JNA Library to expose the extern-C FFI definitions.
88-
// This is an implementation detail which will be called internally by the public API.
89-
internal interface UniffiLib : Library {
90-
companion object {
91-
internal val INSTANCE: UniffiLib by lazy {
92-
val componentName = "{{ ci.namespace() }}"
93-
// For large crates we prevent `MethodTooLargeException` (see #2340)
94-
// N.B. the name of the extension is very misleading, since it is
95-
// rather `InterfaceTooLargeException`, caused by too many methods
96-
// in the interface for large crates.
97-
//
98-
// By splitting the otherwise huge interface into two parts
99-
// * UniffiLib (this)
100-
// * IntegrityCheckingUniffiLib
101-
// And all checksum methods are put into `IntegrityCheckingUniffiLib`
102-
// we allow for ~2x as many methods in the UniffiLib interface.
103-
//
104-
// Thus we first load the library with `loadIndirect` as `IntegrityCheckingUniffiLib`
105-
// so that we can (optionally!) call `uniffiCheckApiChecksums`...
106-
loadIndirect<IntegrityCheckingUniffiLib>(componentName)
107-
.also { lib: IntegrityCheckingUniffiLib ->
108-
uniffiCheckContractApiVersion(lib)
73+
//
74+
// Note: above all written when we used JNA's `loadIndirect` etc.
75+
// We now use JNA's "direct mapping" - unclear if same considerations apply exactly.
76+
internal object IntegrityCheckingUniffiLib {
77+
init {
78+
Native.register(findLibraryName(componentName = "{{ ci.namespace() }}"))
79+
uniffiCheckContractApiVersion(this)
10980
{%- if !config.omit_checksums %}
110-
uniffiCheckApiChecksums(lib)
81+
uniffiCheckApiChecksums(this)
11182
{%- endif %}
112-
}
113-
// ... and then we load the library as `UniffiLib`
114-
// N.B. we cannot use `loadIndirect` once and then try to cast it to `UniffiLib`
115-
// => results in `java.lang.ClassCastException: com.sun.proxy.$Proxy cannot be cast to ...`
116-
// error. So we must call `loadIndirect` twice. For crates large enough
117-
// to trigger this issue, the performance impact is negligible, running on
118-
// a macOS M1 machine the `loadIndirect` call takes ~50ms.
119-
val lib = loadIndirect<UniffiLib>(componentName)
120-
// No need to check the contract version and checksums, since
121-
// we already did that with `IntegrityCheckingUniffiLib` above.
122-
{% for fn_item in self.initialization_fns(ci) -%}
123-
{{ fn_item }}
124-
{% endfor -%}
125-
// Loading of library with integrity check done.
126-
lib
127-
}
128-
{% if ci.contains_object_types() %}
129-
// The Cleaner for the whole library
130-
internal val CLEANER: UniffiCleaner by lazy {
131-
UniffiCleaner.create()
132-
}
133-
{%- endif %}
13483
}
84+
{% filter indent(4) %}
85+
{%- call decl_kotlin_functions(ci.iter_ffi_function_integrity_checks()) %}
86+
{% endfilter %}
87+
}
13588

136-
// FFI functions
137-
{# newline below before call decl_kotlin_functions is needed #}
89+
internal object UniffiLib {
90+
{% if ci.contains_object_types() %}
91+
// The Cleaner for the whole library
92+
internal val CLEANER: UniffiCleaner by lazy {
93+
UniffiCleaner.create()
94+
}
95+
{% endif %}
13896

97+
init {
98+
Native.register(findLibraryName(componentName = "{{ ci.namespace() }}"))
99+
{% for fn_item in self.initialization_fns(ci) -%}
100+
{{ fn_item }}
101+
{% endfor %}
102+
}
103+
{#- XXX - this `filter indent` doesn't seem to work, even though the one above does? #}
104+
{% filter indent(4) %}
139105
{%- call decl_kotlin_functions(ci.iter_ffi_function_definitions_excluding_integrity_checks()) %}
106+
{% endfilter %}
140107
}
141108
142109
private fun uniffiCheckContractApiVersion(lib: IntegrityCheckingUniffiLib) {
@@ -164,5 +131,8 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) {
164131
* @suppress
165132
*/
166133
public fun uniffiEnsureInitialized() {
167-
UniffiLib.INSTANCE
134+
IntegrityCheckingUniffiLib
135+
// UniffiLib() initialized as objects are used, but we still need to explicitly
136+
// reference it so initialization across crates works as expected.
137+
UniffiLib
168138
}

uniffi_bindgen/src/bindings/kotlin/templates/ObjectTemplate.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }
208208
return;
209209
}
210210
uniffiRustCall { status ->
211-
UniffiLib.INSTANCE.{{ obj.ffi_object_free().name() }}(handle, status)
211+
UniffiLib.{{ obj.ffi_object_free().name() }}(handle, status)
212212
}
213213
}
214214
}
@@ -221,7 +221,7 @@ open class {{ impl_class_name }}: Disposable, AutoCloseable, {{ interface_name }
221221
throw InternalException("uniffiCloneHandle() called on NoHandle object");
222222
}
223223
return uniffiRustCall() { status ->
224-
UniffiLib.INSTANCE.{{ obj.ffi_object_clone().name() }}(handle, status)
224+
UniffiLib.{{ obj.ffi_object_clone().name() }}(handle, status)
225225
}
226226
}
227227

uniffi_bindgen/src/bindings/kotlin/templates/RustBufferTemplate.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ open class RustBuffer : Structure() {
2525
companion object {
2626
internal fun alloc(size: ULong = 0UL) = uniffiRustCall() { status ->
2727
// Note: need to convert the size to a `Long` value to make this work with JVM.
28-
UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), status)
28+
UniffiLib.{{ ci.ffi_rustbuffer_alloc().name() }}(size.toLong(), status)
2929
}.also {
3030
if(it.data == null) {
3131
throw RuntimeException("RustBuffer.alloc() returned null data pointer (size=${size})")
@@ -41,7 +41,7 @@ open class RustBuffer : Structure() {
4141
}
4242

4343
internal fun free(buf: RustBuffer.ByValue) = uniffiRustCall() { status ->
44-
UniffiLib.INSTANCE.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
44+
UniffiLib.{{ ci.ffi_rustbuffer_free().name() }}(buf, status)
4545
}
4646
}
4747

uniffi_bindgen/src/bindings/kotlin/templates/macros.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
{%- else %}
2727
uniffiRustCall()
2828
{%- endmatch %} { _status ->
29-
UniffiLib.INSTANCE.{{ func.ffi_func().name() }}(
29+
UniffiLib.{{ func.ffi_func().name() }}(
3030
{%- match func.self_type() %}
3131
{%- when Some(Type::Object { .. }) %}
3232
it,
@@ -72,13 +72,13 @@
7272
uniffiRustCallAsync(
7373
{%- if callable.self_type().is_some() %}
7474
callWithHandle { uniffiHandle ->
75-
UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}(
75+
UniffiLib.{{ callable.ffi_func().name() }}(
7676
uniffiHandle,
7777
{% call arg_list_lowered(callable) %}
7878
)
7979
},
8080
{%- else %}
81-
UniffiLib.INSTANCE.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}),
81+
UniffiLib.{{ callable.ffi_func().name() }}({% call arg_list_lowered(callable) %}),
8282
{%- endif %}
8383
{{ callable|async_poll(ci) }},
8484
{{ callable|async_complete(ci) }},

0 commit comments

Comments
 (0)