Skip to main content

prebindgen_jni_runtime/
box_helpers.rs

1//! Cached primitive-boxing helpers for erased-`Object` deliveries.
2//!
3//! Leaves delivered through an erased Kotlin function type (`FunctionN.invoke`,
4//! all parameters `Object`) must box their primitives. Boxing with
5//! `JNIEnv::new_object("java/lang/Integer", "(I)V", …)` resolves the class
6//! (`FindClass`) and the constructor (`GetMethodID`) on **every** call — three
7//! JNI round-trips per boxed leaf, multiplied by the leaf count on every
8//! callback delivery (the subscriber hot path). These helpers resolve each box
9//! class and its static `valueOf` once per process and afterwards box with a
10//! single `CallStaticObjectMethod`. `valueOf` also returns the JVM's interned
11//! boxes where they exist (`Boolean`, small `Integer`/`Long`/`Short`/`Byte`/
12//! `Character` values), so a typical enum-ordinal leaf allocates nothing.
13
14use std::sync::OnceLock;
15
16use jni::{
17    objects::{GlobalRef, JObject, JStaticMethodID},
18    signature::ReturnType,
19    sys::jvalue,
20    JNIEnv,
21};
22
23/// A `java.lang.*` box class pinned by a process-wide `GlobalRef`, plus its
24/// static `valueOf` method ID. The pin keeps the class from unloading, which
25/// is what keeps the cached method ID valid.
26struct BoxClass {
27    class: GlobalRef,
28    value_of: JStaticMethodID,
29}
30
31fn cached<'c>(
32    env: &mut JNIEnv,
33    cell: &'c OnceLock<BoxClass>,
34    class_name: &str,
35    value_of_sig: &str,
36) -> Result<&'c BoxClass, String> {
37    if let Some(b) = cell.get() {
38        return Ok(b);
39    }
40    let class = env
41        .find_class(class_name)
42        .map_err(|e| format!("find box class {class_name}: {e}"))?;
43    let value_of = env
44        .get_static_method_id(&class, "valueOf", value_of_sig)
45        .map_err(|e| format!("resolve {class_name}.valueOf: {e}"))?;
46    let class = env
47        .new_global_ref(&class)
48        .map_err(|e| format!("global-ref box class {class_name}: {e}"))?;
49    // A concurrent first call may already have filled the cell; both values
50    // are equivalent, keep the winner.
51    let _ = cell.set(BoxClass { class, value_of });
52    Ok(cell.get().expect("cell was just set"))
53}
54
55macro_rules! box_helper {
56    ($name:ident, $prim:ty, $field:ident, $class:literal, $sig:literal) => {
57        #[doc = concat!("Box a `", stringify!($prim), "` into `", $class, "` via cached `valueOf`.")]
58        pub fn $name<'local>(
59            env: &mut JNIEnv<'local>,
60            v: $prim,
61        ) -> Result<JObject<'local>, String> {
62            static CELL: OnceLock<BoxClass> = OnceLock::new();
63            let b = cached(env, &CELL, $class, $sig)?;
64            // SAFETY: `value_of` was resolved on this exact class with this
65            // exact `valueOf` signature, and the `GlobalRef` pins the class.
66            unsafe {
67                env.call_static_method_unchecked(
68                    &b.class,
69                    b.value_of,
70                    ReturnType::Object,
71                    &[jvalue { $field: v }],
72                )
73            }
74            .and_then(|r| r.l())
75            .map_err(|e| format!("box {}: {}", $class, e))
76        }
77    };
78}
79
80box_helper!(
81    box_jboolean,
82    jni::sys::jboolean,
83    z,
84    "java/lang/Boolean",
85    "(Z)Ljava/lang/Boolean;"
86);
87box_helper!(
88    box_jbyte,
89    jni::sys::jbyte,
90    b,
91    "java/lang/Byte",
92    "(B)Ljava/lang/Byte;"
93);
94box_helper!(
95    box_jchar,
96    jni::sys::jchar,
97    c,
98    "java/lang/Character",
99    "(C)Ljava/lang/Character;"
100);
101box_helper!(
102    box_jshort,
103    jni::sys::jshort,
104    s,
105    "java/lang/Short",
106    "(S)Ljava/lang/Short;"
107);
108box_helper!(
109    box_jint,
110    jni::sys::jint,
111    i,
112    "java/lang/Integer",
113    "(I)Ljava/lang/Integer;"
114);
115box_helper!(
116    box_jlong,
117    jni::sys::jlong,
118    j,
119    "java/lang/Long",
120    "(J)Ljava/lang/Long;"
121);
122box_helper!(
123    box_jfloat,
124    jni::sys::jfloat,
125    f,
126    "java/lang/Float",
127    "(F)Ljava/lang/Float;"
128);
129box_helper!(
130    box_jdouble,
131    jni::sys::jdouble,
132    d,
133    "java/lang/Double",
134    "(D)Ljava/lang/Double;"
135);