From 885b4239c9c9423ff4e39d51e4dad9e4fbdcf071 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 22:58:29 +0300 Subject: [PATCH] ffi 1.26: hand-roll JavaVM dispatch in sx for env attach MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the JavaVM-side vtable indirection to `library/modules/platform/ android.sx` so the sx caller of `sx_query_safe_insets_jni` (1.25) can obtain a `JNIEnv*` without the C wrapper. `#jni_call` only dispatches through `JNIEnv*`'s vtable (a different table from `JavaVM*`'s), so the JavaVM hop is hand-rolled here. New decls: - `JNI_VERSION_1_6` (0x00010006) and the `ANATIVEACTIVITY_*` byte offsets (8, 24 on 64-bit Android — vm, clazz respectively). - `sx_load_ptr_at(base, offset)` — load a `*void` field at a raw byte offset. Used for both ANativeActivity fields and the JavaVM vtable load. - `sx_load_javavm_fn(vm, slot)` — load function pointer at the given vtable slot. `vm` is `JavaVM*` which points to `JNIInvokeInterface*`; the indirection is `*vm + slot * 8`. - `sx_android_get_env(activity, out_attached)` — calls `GetEnv` (slot 6); on `JNI_EDETACHED` falls through to `AttachCurrentThread` (slot 4), sets `out_attached = true` so caller can balance with `sx_android_detach_env` (slot 5). - `sx_android_activity_clazz(activity)` — reads the jobject at byte offset 24. Chess Android + iOS-sim builds still clean; cross-compile 3/3 green; host 118/119. The new functions dead-strip until step 1.27 wires them into the safe-insets call site in `android.sx::AndroidPlatform.safe_insets`. --- library/modules/platform/android.sx | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/library/modules/platform/android.sx b/library/modules/platform/android.sx index c1c5850..8e51647 100644 --- a/library/modules/platform/android.sx +++ b/library/modules/platform/android.sx @@ -55,6 +55,82 @@ AMotionEvent_getY :: (event: *void, pointer_index: u64) -> f32 #foreig sx_android_query_safe_insets :: (activity: *void, top: *s32, left: *s32, bottom: *s32, right: *s32) -> void #foreign; sx_android_install_input_handler :: (app: *void, handler: (*void, *void) -> s32) -> void #foreign; +// JavaVM vtable indirection — used to attach the calling thread to +// the JVM and recover a `JNIEnv*` for it. `#jni_call` only handles +// `JNIEnv*` dispatch (a different vtable), so the JavaVM hop is +// hand-rolled here. +// +// Slot indices match `JNIInvokeInterface_` in ``: +// 3 DestroyJavaVM, 4 AttachCurrentThread, 5 DetachCurrentThread, +// 6 GetEnv, 7 AttachCurrentThreadAsDaemon. +JNI_VERSION_1_6 :: 0x00010006; + +// Byte offsets into `ANativeActivity` on 64-bit Android (the only +// Android ABI we target). `vm` is the JavaVM*, `clazz` is the +// activity's jobject. The C struct layout in +// `` is the source of truth; these +// offsets MUST track that. +ANATIVEACTIVITY_VM_OFFSET :: 8; +ANATIVEACTIVITY_CLAZZ_OFFSET :: 24; + +// Load a `*void` field at `base + byte_offset`. Plumbing for raw +// struct access from foreign pointers. +sx_load_ptr_at :: (base: *void, byte_offset: usize) -> *void { + addr : usize = xx base; + slot : **void = xx (addr + byte_offset); + slot.*; +} + +// Load a JavaVM function pointer at the given vtable slot index. +// `vm` is the `JavaVM*` (which points to `JNIInvokeInterface*`), so +// the indirection is `*vm + slot * sizeof(ptr)`. +sx_load_javavm_fn :: (vm: *void, slot: usize) -> *void { + vtable_pp : **void = xx vm; + vtable : *void = vtable_pp.*; + addr : usize = xx vtable; + fn_slot : **void = xx (addr + slot * 8); + fn_slot.*; +} + +// Attach the current thread to the JVM if needed, hand back a +// `JNIEnv*`. `out_attached` is set to true when this call had to do +// the attach (caller should match with `sx_android_detach_env`). +// Returns null on failure (no VM, or attach refused). +sx_android_get_env :: (activity: *void, out_attached: *bool) -> *void { + inline if OS != .android { return null; } + out_attached.* = false; + if activity == null { return null; } + vm := sx_load_ptr_at(activity, ANATIVEACTIVITY_VM_OFFSET); + if vm == null { return null; } + + env : *void = null; + get_env_fn : (*void, **void, s32) -> s32 = xx sx_load_javavm_fn(vm, 6); + if get_env_fn(vm, @env, xx JNI_VERSION_1_6) == 0 { return env; } + + attach_fn : (*void, **void, *void) -> s32 = xx sx_load_javavm_fn(vm, 4); + if attach_fn(vm, @env, null) != 0 { return null; } + out_attached.* = true; + env; +} + +sx_android_detach_env :: (activity: *void) { + inline if OS != .android { return; } + if activity == null { return; } + vm := sx_load_ptr_at(activity, ANATIVEACTIVITY_VM_OFFSET); + if vm == null { return; } + detach_fn : (*void) -> s32 = xx sx_load_javavm_fn(vm, 5); + detach_fn(vm); +} + +// Read the activity's `clazz` jobject (the Java-side activity +// reference). Wrapper around the raw byte-offset load so the call +// site reads naturally. +sx_android_activity_clazz :: (activity: *void) -> *void { + inline if OS != .android { return null; } + if activity == null { return null; } + sx_load_ptr_at(activity, ANATIVEACTIVITY_CLAZZ_OFFSET); +} + // sx-side reimplementation of the JNI dispatch chain inside // `sx_android_query_safe_insets`. Caller provides an already-attached // `JNIEnv*` and the activity's `clazz` jobject. Outputs physical-pixel