#include <jni.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdatomic.h>
#include "{{ module_name }}.h"
static inline bool boltffi_exception_pending(JNIEnv* env) {
return (*env)->ExceptionCheck(env);
}
static inline bool boltffi_consume_pending_exception(JNIEnv* env) {
if (!boltffi_exception_pending(env)) return false;
(*env)->ExceptionClear(env);
return true;
}
static inline void boltffi_throw_out_of_memory(JNIEnv* env, const char* message) {
jclass oom_class = (*env)->FindClass(env, "java/lang/OutOfMemoryError");
if (oom_class == NULL) return;
(*env)->ThrowNew(env, oom_class, message);
(*env)->DeleteLocalRef(env, oom_class);
}
static inline void boltffi_throw_illegal_argument(JNIEnv* env, const char* message) {
jclass exception_class = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
if (exception_class == NULL) return;
(*env)->ThrowNew(env, exception_class, message);
(*env)->DeleteLocalRef(env, exception_class);
}
static inline bool boltffi_try_jlong_to_usize(jlong value, uintptr_t* out_value) {
if (value < 0) return false;
uint64_t unsigned_value = (uint64_t)value;
if (unsigned_value > (uint64_t)UINTPTR_MAX) return false;
*out_value = (uintptr_t)unsigned_value;
return true;
}
static inline jbyteArray boltffi_buf_to_jbytearray(JNIEnv* env, FfiBuf_u8 buf) {
if (buf.ptr == NULL) {
if (buf.len != 0) {
boltffi_throw_out_of_memory(env, "BoltFFI buffer pointer was null");
}
return NULL;
}
if (buf.len > (size_t)INT32_MAX) {
{{ prefix }}_free_buf(buf);
boltffi_throw_out_of_memory(env, "BoltFFI buffer too large for Java byte array");
return NULL;
}
jsize len = (jsize)buf.len;
jbyteArray arr = (*env)->NewByteArray(env, len);
if (arr == NULL) {
{{ prefix }}_free_buf(buf);
return NULL;
}
(*env)->SetByteArrayRegion(env, arr, 0, len, (const jbyte*)buf.ptr);
{{ prefix }}_free_buf(buf);
if (boltffi_exception_pending(env)) {
(*env)->DeleteLocalRef(env, arr);
return NULL;
}
return arr;
}
static inline jbyteArray boltffi_status_buf_to_jbytearray(JNIEnv* env, FfiStatus status, FfiBuf_u8 buf) {
if (status.code != 0) {
if (buf.ptr != NULL) {
{{ prefix }}_free_buf(buf);
}
return NULL;
}
return boltffi_buf_to_jbytearray(env, buf);
}
static inline bool boltffi_lookup_static_method(
JNIEnv* env,
jclass cls,
const char* name,
const char* signature,
jmethodID* out_method
) {
*out_method = (*env)->GetStaticMethodID(env, cls, name, signature);
if (*out_method != NULL) return true;
boltffi_consume_pending_exception(env);
return false;
}
typedef enum {
BOLTFFI_GLOBAL_CLASS_OK = 0,
BOLTFFI_GLOBAL_CLASS_MISSING = 1,
BOLTFFI_GLOBAL_CLASS_FATAL = 2
} BoltFFIGlobalClassResult;
static inline BoltFFIGlobalClassResult boltffi_lookup_global_class(
JNIEnv* env,
const char* class_name,
jclass* out_class
) {
*out_class = NULL;
jclass local_class = (*env)->FindClass(env, class_name);
if (local_class == NULL) {
boltffi_consume_pending_exception(env);
return BOLTFFI_GLOBAL_CLASS_MISSING;
}
jclass global_class = (*env)->NewGlobalRef(env, local_class);
(*env)->DeleteLocalRef(env, local_class);
if (global_class == NULL) {
boltffi_consume_pending_exception(env);
return BOLTFFI_GLOBAL_CLASS_FATAL;
}
*out_class = global_class;
return BOLTFFI_GLOBAL_CLASS_OK;
}
typedef enum {
BOLTFFI_STATIC_CALL_CACHE_UNINIT = 0,
BOLTFFI_STATIC_CALL_CACHE_INITING = 1,
BOLTFFI_STATIC_CALL_CACHE_READY = 2,
BOLTFFI_STATIC_CALL_CACHE_FAILED = 3
} BoltFFIStaticCallCacheState;
typedef struct {
atomic_int state;
jclass class_ref;
jmethodID method;
} BoltFFIStaticCallCache;
#define BOLTFFI_STATIC_CALL_CACHE_INIT { 0, NULL, NULL }
static inline bool boltffi_static_call_cache_ensure(
JNIEnv* env,
BoltFFIStaticCallCache* cache,
const char* class_name,
const char* method_name,
const char* method_signature
) {
int state = atomic_load_explicit(&cache->state, memory_order_acquire);
if (state == BOLTFFI_STATIC_CALL_CACHE_READY) return true;
if (state == BOLTFFI_STATIC_CALL_CACHE_FAILED) return false;
int expected = BOLTFFI_STATIC_CALL_CACHE_UNINIT;
if (atomic_compare_exchange_strong_explicit(
&cache->state,
&expected,
BOLTFFI_STATIC_CALL_CACHE_INITING,
memory_order_acq_rel,
memory_order_acquire)) {
jclass class_ref = NULL;
jmethodID method = NULL;
BoltFFIGlobalClassResult class_result =
boltffi_lookup_global_class(env, class_name, &class_ref);
if (class_result != BOLTFFI_GLOBAL_CLASS_OK) {
cache->class_ref = NULL;
cache->method = NULL;
atomic_store_explicit(
&cache->state,
BOLTFFI_STATIC_CALL_CACHE_FAILED,
memory_order_release
);
return false;
}
if (!boltffi_lookup_static_method(env, class_ref, method_name, method_signature, &method)) {
(*env)->DeleteGlobalRef(env, class_ref);
cache->class_ref = NULL;
cache->method = NULL;
atomic_store_explicit(
&cache->state,
BOLTFFI_STATIC_CALL_CACHE_FAILED,
memory_order_release
);
return false;
}
cache->class_ref = class_ref;
cache->method = method;
atomic_store_explicit(
&cache->state,
BOLTFFI_STATIC_CALL_CACHE_READY,
memory_order_release
);
return true;
}
do {
state = atomic_load_explicit(&cache->state, memory_order_acquire);
} while (state == BOLTFFI_STATIC_CALL_CACHE_INITING);
return state == BOLTFFI_STATIC_CALL_CACHE_READY;
}
static inline void boltffi_static_call_cache_reset(JNIEnv* env, BoltFFIStaticCallCache* cache) {
if (cache->class_ref != NULL) {
(*env)->DeleteGlobalRef(env, cache->class_ref);
cache->class_ref = NULL;
}
cache->method = NULL;
atomic_store_explicit(&cache->state, BOLTFFI_STATIC_CALL_CACHE_UNINIT, memory_order_release);
}
{%- if has_async %}
static JavaVM* g_jvm = NULL;
static jclass g_callback_class = NULL;
static jmethodID g_callback_method = NULL;
typedef enum {
BOLTFFI_CALLBACK_INIT_OK = 0,
BOLTFFI_CALLBACK_INIT_SKIPPED = 1,
BOLTFFI_CALLBACK_INIT_FATAL = 2
} BoltFFICallbackInitResult;
static jint boltffi_attach_current_thread(JavaVM* vm, JNIEnv** env) {
#if defined(__ANDROID__)
return (*vm)->AttachCurrentThread(vm, env, NULL);
#else
return (*vm)->AttachCurrentThread(vm, (void**)env, NULL);
#endif
}
{%- for cb in callback_traits %}
static BoltFFICallbackInitResult init_{{ cb.trait_name }}_callbacks(JNIEnv* env);
{%- endfor %}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
g_jvm = vm;
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
if (boltffi_lookup_global_class(env, "{{ package_path }}/Native", &g_callback_class) != BOLTFFI_GLOBAL_CLASS_OK) {
g_callback_class = NULL;
return JNI_ERR;
}
if (!boltffi_lookup_static_method(env, g_callback_class, "boltffiFutureContinuationCallback", "(JB)V", &g_callback_method)) {
(*env)->DeleteGlobalRef(env, g_callback_class);
g_callback_class = NULL;
g_callback_method = NULL;
return JNI_ERR;
}
{%- for cb in callback_traits %}
if (init_{{ cb.trait_name }}_callbacks(env) == BOLTFFI_CALLBACK_INIT_FATAL) {
return JNI_ERR;
}
{%- endfor %}
return JNI_VERSION_1_6;
}
static void boltffi_jni_continuation_callback(uint64_t handle, int8_t poll_result) {
JNIEnv* env;
int attached = 0;
jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
return;
}
attached = 1;
} else if (get_env_result != JNI_OK) {
return;
}
(*env)->CallStaticVoidMethod(env, g_callback_class, g_callback_method, (jlong)handle, (jbyte)poll_result);
boltffi_consume_pending_exception(env);
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
}
{%- endif %}
{%- for trampoline in closure_trampolines %}
static BoltFFIStaticCallCache g_{{ trampoline.signature_id }}_cache = BOLTFFI_STATIC_CALL_CACHE_INIT;
static {{ trampoline.c_return_type() }} {{ trampoline.trampoline_name }}(void* user_data{{ trampoline.c_params }}) {
JNIEnv* env;
int attached = 0;
jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
{%- if trampoline.is_void() %}
return;
{%- else %}
{{ trampoline.c_return_type() }} zero = {0}; return zero;
{%- endif %}
}
attached = 1;
} else if (get_env_result != JNI_OK) {
{%- if trampoline.is_void() %}
return;
{%- else %}
{{ trampoline.c_return_type() }} zero = {0}; return zero;
{%- endif %}
}
jlong handle = (jlong)(uintptr_t)user_data;
{%- for rp in trampoline.record_params %}
jobject buf_p{{ rp.index }} = (*env)->NewDirectByteBuffer(env, (void*)p{{ rp.index }}_ptr, (jlong)p{{ rp.index }}_len);
{%- endfor %}
if (!boltffi_static_call_cache_ensure(env, &g_{{ trampoline.signature_id }}_cache, "{{ trampoline.callbacks_class_jni_path }}", "call", "(J{{ trampoline.jni_params_signature }}){{ trampoline.jni_return_signature() }}")) {
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
{%- if trampoline.is_void() %}
return;
{%- else %}
{{ trampoline.c_return_type() }} zero = {0}; return zero;
{%- endif %}
}
{%- if trampoline.is_void() %}
(*env)->CallStaticVoidMethod(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
if (boltffi_consume_pending_exception(env)) {
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return;
}
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
{%- else %}
{%- if trampoline.is_wire_encoded_return() %}
jbyteArray _jarr = (jbyteArray)(*env)->{{ trampoline.jni_call_method() }}(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
FfiBuf_u8 _result = {0};
if (boltffi_consume_pending_exception(env)) {
if (_jarr != NULL) (*env)->DeleteLocalRef(env, _jarr);
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return _result;
}
if (_jarr != NULL) {
jsize _len = (*env)->GetArrayLength(env, _jarr);
uint8_t* _ptr = (uint8_t*)malloc((size_t)_len);
if (_ptr != NULL) {
(*env)->GetByteArrayRegion(env, _jarr, 0, _len, (jbyte*)_ptr);
_result.ptr = _ptr;
_result.len = (size_t)_len;
_result.cap = (size_t)_len;
_result.align = 1;
}
(*env)->DeleteLocalRef(env, _jarr);
}
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return _result;
{%- elif trampoline.is_callback_handle_return() %}
jlong _handle_val = (*env)->CallStaticLongMethod(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
if (boltffi_consume_pending_exception(env)) {
BoltFFICallbackHandle _zero = {0};
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return _zero;
}
BoltFFICallbackHandle _result = {{ trampoline.callback_create_fn() }}((uint64_t)_handle_val);
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return _result;
{%- else %}
{{ trampoline.c_return_type() }} _result = {{ trampoline.jni_return_cast() }}(*env)->{{ trampoline.jni_call_method() }}(env, g_{{ trampoline.signature_id }}_cache.class_ref, g_{{ trampoline.signature_id }}_cache.method, handle{% if !trampoline.jni_call_args.is_empty() %}, {{ trampoline.jni_call_args }}{% endif %});
if (boltffi_consume_pending_exception(env)) {
{{ trampoline.c_return_type() }} _zero = {0};
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return _zero;
}
{%- for rp in trampoline.record_params %}
if (buf_p{{ rp.index }} != NULL) (*env)->DeleteLocalRef(env, buf_p{{ rp.index }});
{%- endfor %}
if (attached) {
(*g_jvm)->DetachCurrentThread(g_jvm);
}
return _result;
{%- endif %}
{%- endif %}
}
{%- endfor %}
{%- for wire_fn in wire_functions %}
{{ wire_fn }}
{%- endfor %}
JNIEXPORT jbyteArray JNICALL Java_{{ jni_prefix }}_Native_{{ prefix }}_1last_1error_1message(JNIEnv *env, jclass cls) {
FfiString out = { 0 };
FfiStatus status = {{ prefix }}_last_error_message(&out);
if (status.code != 0 || out.ptr == NULL || out.len == 0) {
{{ prefix }}_free_string(out);
return (*env)->NewByteArray(env, 0);
}
jbyteArray result = (*env)->NewByteArray(env, (jsize)out.len);
if (result != NULL) {
(*env)->SetByteArrayRegion(env, result, 0, (jsize)out.len, (const jbyte*)out.ptr);
}
{{ prefix }}_free_string(out);
return result;
}
{%- for func in functions %}
JNIEXPORT {{ func.jni_return }} JNICALL {{ func.jni_name }}(JNIEnv *env, jclass cls{{ func.jni_params }}) {
{%- if func.return_kind.is_void() %}
{{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- else %}
{{ func.jni_return }} _result = {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
{%- if func.jni_return == "jboolean" %}
return (jboolean)_result;
{%- else %}
return _result;
{%- endif %}
{%- endif %}
}
{%- endfor %}
{%- for class in classes %}
{%- for ctor in class.ctors %}
{{ ctor }}
{%- endfor %}
JNIEXPORT void JNICALL Java_{{ class.jni_prefix }}_Native_{{ class.jni_ffi_prefix }}_1free(JNIEnv *env, jclass cls, jlong handle) {
if (handle != 0) {{ class.ffi_prefix }}_free((void*)handle);
}
{%- for wire_method in class.wire_methods %}
{{ wire_method }}
{%- endfor %}
{%- for func in class.async_methods %}
JNIEXPORT jlong JNICALL {{ func.jni_create_name }}(JNIEnv *env, jclass cls, jlong handle{{ func.jni_params }}) {
if (handle == 0) return 0;
bool _boltffi_input_error = false;
{%- for param in func.params %}
{%- if param.is_string() %}
const char* _{{ param.name }}_c = {{ param.name }} ? (*env)->GetStringUTFChars(env, {{ param.name }}, NULL) : NULL;
if ({{ param.name }} != NULL && _{{ param.name }}_c == NULL) _boltffi_input_error = true;
{%- endif %}
{%- if param.is_primitive_array() %}
uintptr_t _{{ param.name }}_len = (uintptr_t)(*env)->GetArrayLength(env, {{ param.name }});
{{ param.array_c_type() }}* _{{ param.name }}_ptr = ({{ param.array_c_type() }}*)(*env)->{{ param.array_get_elements_fn() }}(env, {{ param.name }}, NULL);
if (_{{ param.name }}_ptr == NULL) { _{{ param.name }}_len = 0; _boltffi_input_error = true; }
{%- endif %}
{%- if param.is_buffer() %}
jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
uintptr_t _{{ param.name }}_len = (_{{ param.name }}_ptr && _{{ param.name }}_size > 0) ? (uintptr_t)_{{ param.name }}_size : 0;
{%- endif %}
{%- if param.is_composite() %}
jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
{{ param.composite_c_type() }} _{{ param.name }}_val;
if (_{{ param.name }}_ptr && _{{ param.name }}_size >= (jlong)sizeof({{ param.composite_c_type() }})) {
memcpy(&_{{ param.name }}_val, _{{ param.name }}_ptr, sizeof({{ param.composite_c_type() }}));
} else {
memset(&_{{ param.name }}_val, 0, sizeof({{ param.composite_c_type() }}));
_boltffi_input_error = true;
}
{%- endif %}
{%- endfor %}
RustFutureHandle _handle = 0;
if (_boltffi_input_error) goto boltffi_input_cleanup;
_handle = {{ func.ffi_name }}((void*)handle{% for param in func.params %}, {{ param.ffi_arg() }}{% endfor %});
boltffi_input_cleanup:
{%- for param in func.params %}
{%- if param.is_primitive_array() %}
if (_{{ param.name }}_ptr != NULL) (*env)->{{ param.array_release_elements_fn() }}(env, {{ param.name }}, ({{ param.array_elements_ptr_type() }})_{{ param.name }}_ptr, {{ param.array_release_mode() }});
{%- endif %}
{%- if param.is_string() %}
if (_{{ param.name }}_c != NULL) (*env)->ReleaseStringUTFChars(env, {{ param.name }}, _{{ param.name }}_c);
{%- endif %}
{%- endfor %}
if (_boltffi_input_error) return 0;
return (jlong)_handle;
}
JNIEXPORT void JNICALL {{ func.jni_poll_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future, jlong contHandle) {
{{ func.ffi_poll }}((RustFutureHandle)future, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}
JNIEXPORT {{ func.complete_kind.jni_return() }} JNICALL {{ func.jni_complete_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
{%- if func.complete_kind.is_void() %}
FfiStatus _status;
{{ func.ffi_complete }}((RustFutureHandle)future, &_status);
{%- elif func.complete_kind.is_wire_encoded() %}
FfiStatus _status;
FfiBuf_u8 _buf = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
return boltffi_status_buf_to_jbytearray(env, _status, _buf);
{%- else %}
FfiStatus _status;
{{ func.complete_kind.c_type() }} _result = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
return ({{ func.complete_kind.jni_return() }})_result;
{%- endif %}
}
JNIEXPORT void JNICALL {{ func.jni_cancel_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
{{ func.ffi_cancel }}((RustFutureHandle)future);
}
JNIEXPORT void JNICALL {{ func.jni_free_name }}(JNIEnv *env, jclass cls, jlong handle, jlong future) {
{{ func.ffi_free }}((RustFutureHandle)future);
}
{%- endfor %}
{%- for stream in class.streams %}
JNIEXPORT jlong JNICALL {{ stream.subscribe_jni }}(JNIEnv *env, jclass cls, jlong handle) {
if (handle == 0) return 0;
SubscriptionHandle _subscription = {{ stream.subscribe_ffi }}((void*)handle);
return (jlong)_subscription;
}
JNIEXPORT void JNICALL {{ stream.poll_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jlong contHandle) {
if (subscription_handle == 0) return;
{{ stream.poll_ffi }}((SubscriptionHandle)subscription_handle, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}
JNIEXPORT jbyteArray JNICALL {{ stream.pop_batch_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jlong max_count) {
if (subscription_handle == 0) return NULL;
uintptr_t _max_count = 0;
if (!boltffi_try_jlong_to_usize(max_count, &_max_count)) {
boltffi_throw_illegal_argument(env, "max_count must be a non-negative size");
return NULL;
}
FfiBuf_u8 _buf = {{ stream.pop_batch_ffi }}((SubscriptionHandle)subscription_handle, _max_count);
if (_buf.len == 0) {
if (_buf.ptr != NULL) {{ prefix }}_free_buf(_buf);
return (*env)->NewByteArray(env, 0);
}
return boltffi_buf_to_jbytearray(env, _buf);
}
JNIEXPORT jint JNICALL {{ stream.wait_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle, jint timeout) {
if (subscription_handle == 0) return -1;
int32_t _result = {{ stream.wait_ffi }}((SubscriptionHandle)subscription_handle, (uint32_t)timeout);
return (jint)_result;
}
JNIEXPORT void JNICALL {{ stream.unsubscribe_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle) {
if (subscription_handle == 0) return;
{{ stream.unsubscribe_ffi }}((SubscriptionHandle)subscription_handle);
}
JNIEXPORT void JNICALL {{ stream.free_jni }}(JNIEnv *env, jclass cls, jlong subscription_handle) {
if (subscription_handle == 0) return;
{{ stream.free_ffi }}((SubscriptionHandle)subscription_handle);
}
{%- endfor %}
{%- endfor %}
{%- for func in async_functions %}
JNIEXPORT jlong JNICALL {{ func.jni_create_name }}(JNIEnv *env, jclass cls{{ func.jni_params }}) {
bool _boltffi_input_error = false;
{%- for param in func.params %}
{%- if param.is_string() %}
const char* _{{ param.name }}_c = {{ param.name }} ? (*env)->GetStringUTFChars(env, {{ param.name }}, NULL) : NULL;
if ({{ param.name }} != NULL && _{{ param.name }}_c == NULL) _boltffi_input_error = true;
{%- endif %}
{%- if param.is_primitive_array() %}
uintptr_t _{{ param.name }}_len = (uintptr_t)(*env)->GetArrayLength(env, {{ param.name }});
{{ param.array_c_type() }}* _{{ param.name }}_ptr = ({{ param.array_c_type() }}*)(*env)->{{ param.array_get_elements_fn() }}(env, {{ param.name }}, NULL);
if (_{{ param.name }}_ptr == NULL) { _{{ param.name }}_len = 0; _boltffi_input_error = true; }
{%- endif %}
{%- if param.is_buffer() %}
jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
uintptr_t _{{ param.name }}_len = (_{{ param.name }}_ptr && _{{ param.name }}_size > 0) ? (uintptr_t)_{{ param.name }}_size : 0;
{%- endif %}
{%- if param.is_composite() %}
jlong _{{ param.name }}_size = (*env)->GetDirectBufferCapacity(env, {{ param.name }});
uint8_t* _{{ param.name }}_ptr = (uint8_t*)(*env)->GetDirectBufferAddress(env, {{ param.name }});
{{ param.composite_c_type() }} _{{ param.name }}_val;
if (_{{ param.name }}_ptr && _{{ param.name }}_size >= (jlong)sizeof({{ param.composite_c_type() }})) {
memcpy(&_{{ param.name }}_val, _{{ param.name }}_ptr, sizeof({{ param.composite_c_type() }}));
} else {
memset(&_{{ param.name }}_val, 0, sizeof({{ param.composite_c_type() }}));
_boltffi_input_error = true;
}
{%- endif %}
{%- endfor %}
RustFutureHandle _handle = 0;
if (_boltffi_input_error) goto boltffi_input_cleanup;
_handle = {{ func.ffi_name }}({% for param in func.params %}{{ param.ffi_arg() }}{% if !loop.last %}, {% endif %}{% endfor %});
boltffi_input_cleanup:
{%- for param in func.params %}
{%- if param.is_primitive_array() %}
if (_{{ param.name }}_ptr != NULL) (*env)->{{ param.array_release_elements_fn() }}(env, {{ param.name }}, ({{ param.array_elements_ptr_type() }})_{{ param.name }}_ptr, {{ param.array_release_mode() }});
{%- endif %}
{%- if param.is_string() %}
if (_{{ param.name }}_c != NULL) (*env)->ReleaseStringUTFChars(env, {{ param.name }}, _{{ param.name }}_c);
{%- endif %}
{%- endfor %}
if (_boltffi_input_error) return 0;
return (jlong)_handle;
}
JNIEXPORT void JNICALL {{ func.jni_poll_name }}(JNIEnv *env, jclass cls, jlong future, jlong contHandle) {
{{ func.ffi_poll }}((RustFutureHandle)future, (uint64_t)contHandle, boltffi_jni_continuation_callback);
}
JNIEXPORT {{ func.complete_kind.jni_return() }} JNICALL {{ func.jni_complete_name }}(JNIEnv *env, jclass cls, jlong future) {
{%- if func.complete_kind.is_void() %}
FfiStatus _status;
{{ func.ffi_complete }}((RustFutureHandle)future, &_status);
{%- elif func.complete_kind.is_wire_encoded() %}
FfiStatus _status;
FfiBuf_u8 _buf = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
return boltffi_status_buf_to_jbytearray(env, _status, _buf);
{%- else %}
FfiStatus _status;
{{ func.complete_kind.c_type() }} _result = {{ func.ffi_complete }}((RustFutureHandle)future, &_status);
return ({{ func.complete_kind.jni_return() }})_result;
{%- endif %}
}
JNIEXPORT void JNICALL {{ func.jni_cancel_name }}(JNIEnv *env, jclass cls, jlong future) {
{{ func.ffi_cancel }}((RustFutureHandle)future);
}
JNIEXPORT void JNICALL {{ func.jni_free_name }}(JNIEnv *env, jclass cls, jlong future) {
{{ func.ffi_free }}((RustFutureHandle)future);
}
{%- endfor %}
{%- for cb in callback_traits %}
static jclass g_{{ cb.trait_name }}_callbacks_class = NULL;
static jmethodID g_{{ cb.trait_name }}_free_method = NULL;
static jmethodID g_{{ cb.trait_name }}_clone_method = NULL;
{%- for method in cb.sync_methods %}
static jmethodID g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- for method in cb.async_methods %}
static jmethodID g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
static void {{ cb.trait_name }}_vtable_free(uint64_t handle) {
JNIEnv* env;
int attached = 0;
jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return;
attached = 1;
} else if (get_env_result != JNI_OK) return;
(*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_free_method, (jlong)handle);
boltffi_consume_pending_exception(env);
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}
static uint64_t {{ cb.trait_name }}_vtable_clone(uint64_t handle) {
JNIEnv* env;
int attached = 0;
jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) return 0;
attached = 1;
} else if (get_env_result != JNI_OK) return 0;
jlong result = (*env)->CallStaticLongMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_clone_method, (jlong)handle);
if (boltffi_consume_pending_exception(env)) {
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
return 0;
}
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
return (uint64_t)result;
}
{%- for method in cb.sync_methods %}
static void {{ cb.trait_name }}_vtable_{{ method.ffi_name }}(uint64_t handle{% for param in method.c_params %}, {{ param.c_type }} {{ param.name }}{% endfor %}, FfiStatus* status) {
JNIEnv* env;
int attached = 0;
jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
status->code = 1;
return;
}
attached = 1;
} else if (get_env_result != JNI_OK) {
status->code = 1;
return;
}
{%- for line in method.setup_lines %}
{{ line }}
{%- endfor %}
{%- if method.is_wire_encoded_return() %}
*{{ method.out_ptr_name() }} = NULL;
*{{ method.out_len_name() }} = 0;
jbyteArray _jarr = (jbyteArray)(*env)->CallStaticObjectMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
if (boltffi_consume_pending_exception(env)) {
status->code = 1;
goto cleanup;
}
if (_jarr != NULL) {
jsize _len = (*env)->GetArrayLength(env, _jarr);
if (_len > 0) {
uint8_t* _ptr = (uint8_t*)malloc((size_t)_len);
(*env)->GetByteArrayRegion(env, _jarr, 0, _len, (jbyte*)_ptr);
if (_ptr == NULL) {
boltffi_throw_out_of_memory(env, "Failed to allocate callback return buffer");
status->code = 1;
(*env)->DeleteLocalRef(env, _jarr);
goto cleanup;
}
*{{ method.out_ptr_name() }} = _ptr;
*{{ method.out_len_name() }} = (uintptr_t)_len;
}
(*env)->DeleteLocalRef(env, _jarr);
}
{%- elif method.has_return() %}
{{ method.jni_return_type() }} _result = (*env)->CallStatic{{ method.jni_call_type() }}Method(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
if (boltffi_consume_pending_exception(env)) {
status->code = 1;
goto cleanup;
}
*{{ method.out_ptr_name() }} = ({{ method.c_return_type() }})_result;
{%- else %}
(*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %});
if (boltffi_consume_pending_exception(env)) {
status->code = 1;
goto cleanup;
}
{%- endif %}
status->code = 0;
cleanup:
{%- for line in method.cleanup_lines %}
{{ line }}
{%- endfor %}
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
}
{%- endfor %}
{%- for method in cb.async_methods %}
static void {{ cb.trait_name }}_vtable_{{ method.ffi_name }}(uint64_t handle{% for param in method.c_params %}, {{ param.c_type }} {{ param.name }}{% endfor %}, void (*callback)(uint64_t{% if method.is_wire() %}, const uint8_t*, size_t{% elif method.has_return() %}, {{ method.return_c_type() }}{% endif %}, FfiStatus), uint64_t callback_data) {
JNIEnv* env;
int attached = 0;
bool _boltffi_dispatch_succeeded = false;
jint get_env_result = (*g_jvm)->GetEnv(g_jvm, (void**)&env, JNI_VERSION_1_6);
if (get_env_result == JNI_EDETACHED) {
if (boltffi_attach_current_thread(g_jvm, &env) != JNI_OK) {
{%- if method.is_wire() %}
callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
{%- elif method.has_return() %}
callback((uint64_t)callback_data, ({{ method.return_c_type() }}){0}, (FfiStatus){.code = 1});
{%- else %}
callback((uint64_t)callback_data, (FfiStatus){.code = 1});
{%- endif %}
return;
}
attached = 1;
} else if (get_env_result != JNI_OK) {
{%- if method.is_wire() %}
callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
{%- elif method.has_return() %}
callback((uint64_t)callback_data, ({{ method.return_c_type() }}){0}, (FfiStatus){.code = 1});
{%- else %}
callback((uint64_t)callback_data, (FfiStatus){.code = 1});
{%- endif %}
return;
}
{%- for line in method.setup_lines %}
{{ line }}
{%- endfor %}
(*env)->CallStaticVoidMethod(env, g_{{ cb.trait_name }}_callbacks_class, g_{{ cb.trait_name }}_{{ method.ffi_name }}_method, (jlong)handle{% for arg in method.jni_args %}, {{ arg }}{% endfor %}, (jlong)callback, (jlong)callback_data);
if (!boltffi_consume_pending_exception(env)) {
_boltffi_dispatch_succeeded = true;
}
cleanup:
{%- for line in method.cleanup_lines %}
{{ line }}
{%- endfor %}
if (attached) (*g_jvm)->DetachCurrentThread(g_jvm);
if (_boltffi_dispatch_succeeded) return;
{%- if method.is_wire() %}
callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
{%- elif method.has_return() %}
callback((uint64_t)callback_data, ({{ method.return_c_type() }}){0}, (FfiStatus){.code = 1});
{%- else %}
callback((uint64_t)callback_data, (FfiStatus){.code = 1});
{%- endif %}
}
{%- endfor %}
static {{ cb.vtable_type }} g_{{ cb.trait_name }}_vtable = {
.free = {{ cb.trait_name }}_vtable_free,
.clone = {{ cb.trait_name }}_vtable_clone,
{%- for method in cb.sync_methods %}
.{{ method.ffi_name }} = {{ cb.trait_name }}_vtable_{{ method.ffi_name }},
{%- endfor %}
{%- for method in cb.async_methods %}
.{{ method.ffi_name }} = {{ cb.trait_name }}_vtable_{{ method.ffi_name }},
{%- endfor %}
};
static BoltFFICallbackInitResult init_{{ cb.trait_name }}_callbacks(JNIEnv* env) {
BoltFFIGlobalClassResult class_result =
boltffi_lookup_global_class(env, "{{ cb.callbacks_class }}", &g_{{ cb.trait_name }}_callbacks_class);
if (class_result == BOLTFFI_GLOBAL_CLASS_MISSING) {
return BOLTFFI_CALLBACK_INIT_SKIPPED;
}
if (class_result != BOLTFFI_GLOBAL_CLASS_OK) {
return BOLTFFI_CALLBACK_INIT_FATAL;
}
if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "free", "(J)V", &g_{{ cb.trait_name }}_free_method)) goto fail;
if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "clone", "(J)J", &g_{{ cb.trait_name }}_clone_method)) goto fail;
{%- for method in cb.sync_methods %}
if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "{{ method.jni_method_name }}", "{{ method.jni_signature }}", &g_{{ cb.trait_name }}_{{ method.ffi_name }}_method)) goto fail;
{%- endfor %}
{%- for method in cb.async_methods %}
if (!boltffi_lookup_static_method(env, g_{{ cb.trait_name }}_callbacks_class, "{{ method.jni_method_name }}", "{{ method.jni_signature }}", &g_{{ cb.trait_name }}_{{ method.ffi_name }}_method)) goto fail;
{%- endfor %}
{{ cb.register_fn }}(&g_{{ cb.trait_name }}_vtable);
return BOLTFFI_CALLBACK_INIT_OK;
fail:
(*env)->DeleteGlobalRef(env, g_{{ cb.trait_name }}_callbacks_class);
g_{{ cb.trait_name }}_callbacks_class = NULL;
return BOLTFFI_CALLBACK_INIT_FATAL;
}
{%- endfor %}
{%- if has_async %}
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM* vm, void* reserved) {
JNIEnv* env;
if ((*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
if (g_callback_class != NULL) {
(*env)->DeleteGlobalRef(env, g_callback_class);
g_callback_class = NULL;
}
g_callback_method = NULL;
{%- for trampoline in closure_trampolines %}
boltffi_static_call_cache_reset(env, &g_{{ trampoline.signature_id }}_cache);
{%- endfor %}
{%- for cb in callback_traits %}
if (g_{{ cb.trait_name }}_callbacks_class != NULL) {
(*env)->DeleteGlobalRef(env, g_{{ cb.trait_name }}_callbacks_class);
g_{{ cb.trait_name }}_callbacks_class = NULL;
}
g_{{ cb.trait_name }}_free_method = NULL;
g_{{ cb.trait_name }}_clone_method = NULL;
{%- for method in cb.sync_methods %}
g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- for method in cb.async_methods %}
g_{{ cb.trait_name }}_{{ method.ffi_name }}_method = NULL;
{%- endfor %}
{%- endfor %}
}
{%- endif %}
{%- for invoker in async_callback_invokers %}
JNIEXPORT void JNICALL {{ invoker.jni_fn_name }}(JNIEnv* env, jclass cls, jlong callback_ptr, jlong callback_data{% if invoker.has_result() %}, {{ invoker.jni_result_type() }} result{% endif %}) {
{%- if invoker.is_wire() %}
void (*callback)(uint64_t, const uint8_t*, size_t, FfiStatus) = (void (*)(uint64_t, const uint8_t*, size_t, FfiStatus))callback_ptr;
if (result == NULL) {
callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
return;
}
jsize _len = (*env)->GetArrayLength(env, result);
jbyte* _ptr = (*env)->GetByteArrayElements(env, result, NULL);
if (_ptr == NULL) {
callback((uint64_t)callback_data, NULL, 0, (FfiStatus){.code = 1});
return;
}
callback((uint64_t)callback_data, (const uint8_t*)_ptr, (size_t)_len, (FfiStatus){.code = 0});
(*env)->ReleaseByteArrayElements(env, result, _ptr, JNI_ABORT);
{%- elif invoker.has_result() %}
(void)env; (void)cls;
void (*callback)(uint64_t, {{ invoker.c_result_type() }}, FfiStatus) = (void (*)(uint64_t, {{ invoker.c_result_type() }}, FfiStatus))callback_ptr;
callback((uint64_t)callback_data, ({{ invoker.c_result_type() }})result, (FfiStatus){.code = 0});
{%- else %}
(void)env; (void)cls;
void (*callback)(uint64_t, FfiStatus) = (void (*)(uint64_t, FfiStatus))callback_ptr;
callback((uint64_t)callback_data, (FfiStatus){.code = 0});
{%- endif %}
}
{%- endfor %}