ffi 1.26: hand-roll JavaVM dispatch in sx for env attach

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`.
This commit is contained in:
agra
2026-05-19 22:58:29 +03:00
parent ba0a1a13e3
commit 885b4239c9

View File

@@ -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 `<jni.h>`:
// 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
// `<android/native_activity.h>` 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