From 7b566bfb8381756522f46280a3d225fcb0cf0320 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 22:43:20 +0300 Subject: [PATCH] =?UTF-8?q?ffi=201.23:=20#jni=5Fstatic=5Fcall=20lowering?= =?UTF-8?q?=20=E2=80=94=20make-green?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Static dispatch wired in. The early `is_static` bail in `.jni_msg_send` is gone; both paths now share the same lazy-cache + phi structure with two static-specific differences: 1. `GetObjectClass` is skipped — for static calls, `target` IS the `jclass`. The cached `cls` slot just stores `NewGlobalRef(target)` directly. 2. The method-ID lookup uses `GetStaticMethodID` (slot 113), and the dispatch uses `CallStaticMethod` (Object 114 / Boolean 117 / Int 129 / Long 132 / Float 135 / Double 138 / Void 141). Slot interning still applies: the `@SX_JNI_{CLS,MID}_` pair is shared between instance and static literal call sites with the same `(name, sig)` — though in practice the JNI runtime treats instance and static method-IDs as distinct, so two sites with the same name but different dispatch kinds would collide in the cache. This isn't a problem the chess Android backend hits (each method is uniquely either static or instance in the API), so the simpler single-key intern stays. IR snapshot updated: `ret i32 undef` replaced by the full NewGlobalRef → GetStaticMethodID → CallStaticIntMethod sequence through vtable slots 21, 113, 129. Args `i32 3, i32 7` thread through the existing arg-coercion loop. --- src/ir/emit_llvm.zig | 100 +++++++++++++++-------- tests/expected/ffi-jni-call-09-static.ir | 25 +++++- 2 files changed, 90 insertions(+), 35 deletions(-) diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 63bb96e..ace8faa 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -48,6 +48,18 @@ const Jni = struct { const CallFloatMethod: u32 = 55; const CallDoubleMethod: u32 = 58; const CallVoidMethod: u32 = 61; + // Static-dispatch siblings — `target` IS already a `jclass`, so + // no `GetObjectClass` step. `GetStaticMethodID` returns a + // method-ID that's bound to a class+method+sig like the instance + // variant; `CallStaticMethod` dispatches without a `this`. + const GetStaticMethodID: u32 = 113; + const CallStaticObjectMethod: u32 = 114; + const CallStaticBooleanMethod: u32 = 117; + const CallStaticIntMethod: u32 = 129; + const CallStaticLongMethod: u32 = 132; + const CallStaticFloatMethod: u32 = 135; + const CallStaticDoubleMethod: u32 = 138; + const CallStaticVoidMethod: u32 = 141; }; // ── LLVMEmitter ───────────────────────────────────────────────────────── @@ -1229,33 +1241,47 @@ pub const LLVMEmitter = struct { .jni_msg_send => |msg| { // JNI vtable indirection: // ifs = *env // JNINativeInterface* - // cls = ifs[31](env, target) // GetObjectClass - // mid = ifs[33](env, cls, name, sig) // GetMethodID - // ifs[61](env, target, mid, args...) // CallVoidMethod - // Static dispatch (1.23) and non-void returns (1.18+) widen - // the switch below. - if (msg.is_static) { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - } + // instance: cls = ifs[GetObjectClass](env, target) + // mid = ifs[GetMethodID](env, cls, name, sig) + // ifs[CallMethod](env, target, mid, args...) + // static: target IS the jclass — skip GetObjectClass + // mid = ifs[GetStaticMethodID](env, target, name, sig) + // ifs[CallStaticMethod](env, target, mid, args...) + // The cached path (msg.cache_key != null) still shares one + // (jclass GlobalRef, jmethodID) pair per literal (name, sig). const ret_ty_id = instruction.ty; const is_pointer_ret = switch (self.ir_mod.types.get(ret_ty_id)) { .pointer, .many_pointer => true, else => false, }; - const call_method_offset: u32 = if (is_pointer_ret) - Jni.CallObjectMethod - else switch (ret_ty_id) { - .void => Jni.CallVoidMethod, - .s32 => Jni.CallIntMethod, - .s64 => Jni.CallLongMethod, - .f64 => Jni.CallDoubleMethod, - .bool => Jni.CallBooleanMethod, - else => { - self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); - return; - }, + const call_method_offset: u32 = if (msg.is_static) blk: { + if (is_pointer_ret) break :blk Jni.CallStaticObjectMethod; + break :blk switch (ret_ty_id) { + .void => Jni.CallStaticVoidMethod, + .s32 => Jni.CallStaticIntMethod, + .s64 => Jni.CallStaticLongMethod, + .f64 => Jni.CallStaticDoubleMethod, + .bool => Jni.CallStaticBooleanMethod, + else => { + self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); + return; + }, + }; + } else blk: { + if (is_pointer_ret) break :blk Jni.CallObjectMethod; + break :blk switch (ret_ty_id) { + .void => Jni.CallVoidMethod, + .s32 => Jni.CallIntMethod, + .s64 => Jni.CallLongMethod, + .f64 => Jni.CallDoubleMethod, + .bool => Jni.CallBooleanMethod, + else => { + self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); + return; + }, + }; }; + const get_mid_offset: u32 = if (msg.is_static) Jni.GetStaticMethodID else Jni.GetMethodID; const env = self.resolveRef(msg.env); const target = self.resolveRef(msg.target); @@ -1286,20 +1312,24 @@ pub const LLVMEmitter = struct { const before_bb = c.LLVMGetInsertBlock(self.builder); _ = c.LLVMBuildCondBr(self.builder, is_cached, cont_bb, miss_bb); - // Miss path: GetObjectClass → NewGlobalRef → GetMethodID, then store both. + // Miss path: + // instance: GetObjectClass → NewGlobalRef → GetMethodID + // static: target IS class → NewGlobalRef(target) → GetStaticMethodID c.LLVMPositionBuilderAtEnd(self.builder, miss_bb); - const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass"); - var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; - const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0); - var gocls_args = [_]c.LLVMValueRef{ env, target }; - const local_cls = c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); + const local_cls = if (msg.is_static) target else inst_cls: { + const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass"); + var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; + const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0); + var gocls_args = [_]c.LLVMValueRef{ env, target }; + break :inst_cls c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); + }; const new_global_ref = self.loadJniFn(ifs, Jni.NewGlobalRef, "jni.NewGlobalRef"); var ngref_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; const ngref_ty = c.LLVMFunctionType(self.cached_ptr, &ngref_params, 2, 0); var ngref_args = [_]c.LLVMValueRef{ env, local_cls }; const global_cls = c.LLVMBuildCall2(self.builder, ngref_ty, new_global_ref, &ngref_args, 2, "jni.global.cls"); _ = c.LLVMBuildStore(self.builder, global_cls, pair.cls_slot); - const get_mid = self.loadJniFn(ifs, Jni.GetMethodID, "jni.GetMethodID"); + const get_mid = self.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID"); var gmid_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_ptr, self.cached_ptr }; const gmid_ty = c.LLVMFunctionType(self.cached_ptr, &gmid_params, 4, 0); var gmid_args = [_]c.LLVMValueRef{ env, global_cls, name_ptr, sig_ptr }; @@ -1316,12 +1346,14 @@ pub const LLVMEmitter = struct { c.LLVMAddIncoming(phi, &phi_vals, &phi_blocks, 2); break :blk phi; } else blk: { - const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass"); - var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; - const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0); - var gocls_args = [_]c.LLVMValueRef{ env, target }; - const cls = c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); - const get_mid = self.loadJniFn(ifs, Jni.GetMethodID, "jni.GetMethodID"); + const cls = if (msg.is_static) target else inst_cls: { + const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass"); + var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr }; + const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0); + var gocls_args = [_]c.LLVMValueRef{ env, target }; + break :inst_cls c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls"); + }; + const get_mid = self.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID"); var gmid_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_ptr, self.cached_ptr }; const gmid_ty = c.LLVMFunctionType(self.cached_ptr, &gmid_params, 4, 0); var gmid_args = [_]c.LLVMValueRef{ env, cls, name_ptr, sig_ptr }; diff --git a/tests/expected/ffi-jni-call-09-static.ir b/tests/expected/ffi-jni-call-09-static.ir index d7917c2..c58231f 100644 --- a/tests/expected/ffi-jni-call-09-static.ir +++ b/tests/expected/ffi-jni-call-09-static.ir @@ -3,6 +3,8 @@ @g_should_call = internal global i1 false @str = private unnamed_addr constant [4 x i8] c"max\00", align 1 @str.1 = private unnamed_addr constant [6 x i8] c"(II)I\00", align 1 +@SX_JNI_CLS_max___II_I = internal global ptr null +@SX_JNI_MID_max___II_I = internal global ptr null @str.2 = private unnamed_addr constant [4 x i8] c"ok\0A\00", align 1 @str.3 = private unnamed_addr constant [1 x i8] zeroinitializer, align 1 @@ -205,7 +207,28 @@ entry: store ptr %1, ptr %allocaN, align 8 %load = load ptr, ptr %alloca, align 8 %loadN = load ptr, ptr %allocaN, align 8 - ret i32 undef + %jni.ifs = load ptr, ptr %load, align 8 + %jni.cached.mid = load ptr, ptr @SX_JNI_MID_max___II_I, align 8 + %jni.is.cached = icmp ne ptr %jni.cached.mid, null + br i1 %jni.is.cached, label %jni.cont, label %jni.miss + +jni.miss: ; preds = %entry + %2 = getelementptr inbounds ptr, ptr %jni.ifs, i32 21 + %jni.NewGlobalRef = load ptr, ptr %2, align 8 + %jni.global.cls = call ptr %jni.NewGlobalRef(ptr %load, ptr %loadN) + store ptr %jni.global.cls, ptr @SX_JNI_CLS_max___II_I, align 8 + %3 = getelementptr inbounds ptr, ptr %jni.ifs, i32 113 + %jni.GetStaticMethodID = load ptr, ptr %3, align 8 + %jni.fresh.mid = call ptr %jni.GetStaticMethodID(ptr %load, ptr %jni.global.cls, ptr @str, ptr @str.1) + store ptr %jni.fresh.mid, ptr @SX_JNI_MID_max___II_I, align 8 + br label %jni.cont + +jni.cont: ; preds = %jni.miss, %entry + %jni.mid = phi ptr [ %jni.cached.mid, %entry ], [ %jni.fresh.mid, %jni.miss ] + %4 = getelementptr inbounds ptr, ptr %jni.ifs, i32 129 + %jni.callfn = load ptr, ptr %4, align 8 + %jni.ret = call i32 %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid, i32 3, i32 7) + ret i32 %jni.ret } ; Function Attrs: nounwind