From c1877fc00e79ba2c800aa0b8fdc7c6787ce46392 Mon Sep 17 00:00:00 2001 From: agra Date: Tue, 19 May 2026 22:28:51 +0300 Subject: [PATCH] ffi: lift JNI vtable offsets into a named-constants struct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The numeric slot indices (21, 31, 33, 49, 61) in the `#jni_call` lowering are JNI-spec constants from `` but appeared as bare magic numbers — only the trailing comment told you which JNI function you were loading. Moving them into a private `const Jni` namespace at file scope makes the call sites self-documenting: loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass") loadJniFn(ifs, Jni.NewGlobalRef, "jni.NewGlobalRef") loadJniFn(ifs, Jni.GetMethodID, "jni.GetMethodID") switch (ret_ty_id) { .void => Jni.CallVoidMethod, .s32 => Jni.CallIntMethod, ... } Also pre-loaded the remaining CallMethod slots (Object, Boolean, Long, Float, Double) so steps 1.19–1.22 just add the corresponding switch arm — no new magic-number lookups in the diff. Behavior-preserving refactor: IR snapshots unchanged, all 113 host tests still pass, both cross-compile tuples still green. --- src/ir/emit_llvm.zig | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 71bf732..2354b63 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -29,6 +29,27 @@ fn isIdentByte(b: u8) bool { return (b >= 'a' and b <= 'z') or (b >= 'A' and b <= 'Z') or (b >= '0' and b <= '9') or b == '_'; } +/// JNI vtable slot offsets — indices into the `JNINativeInterface` +/// function-pointer array reachable via `*env`. Stable per the JNI +/// spec across versions; locked to the documented order in +/// ``. Slot numbers here MUST match the order of fields in +/// the C `JNINativeInterface_` struct. +const Jni = struct { + const NewGlobalRef: u32 = 21; + const GetObjectClass: u32 = 31; + const GetMethodID: u32 = 33; + // CallMethod (instance, varargs variant). Each numeric type + // has its own slot — distinct ABI per return type, so the JNI + // runtime dispatches the right arg-shuffle for each. + const CallObjectMethod: u32 = 34; + const CallBooleanMethod: u32 = 37; + const CallIntMethod: u32 = 49; + const CallLongMethod: u32 = 52; + const CallFloatMethod: u32 = 55; + const CallDoubleMethod: u32 = 58; + const CallVoidMethod: u32 = 61; +}; + // ── LLVMEmitter ───────────────────────────────────────────────────────── // Emits LLVM IR from an IR Module. This is the Phase 3 replacement for // the AST-based codegen. @@ -1219,8 +1240,8 @@ pub const LLVMEmitter = struct { } const ret_ty_id = instruction.ty; const call_method_offset: u32 = switch (ret_ty_id) { - .void => 61, // CallVoidMethod - .s32 => 49, // CallIntMethod + .void => Jni.CallVoidMethod, + .s32 => Jni.CallIntMethod, else => { self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty))); return; @@ -1258,18 +1279,18 @@ pub const LLVMEmitter = struct { // Miss path: GetObjectClass → NewGlobalRef → GetMethodID, then store both. c.LLVMPositionBuilderAtEnd(self.builder, miss_bb); - const get_obj_cls = self.loadJniFn(ifs, 31, "jni.GetObjectClass"); + 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 new_global_ref = self.loadJniFn(ifs, 21, "jni.NewGlobalRef"); + 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, 33, "jni.GetMethodID"); + const get_mid = self.loadJniFn(ifs, Jni.GetMethodID, "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 }; @@ -1286,12 +1307,12 @@ pub const LLVMEmitter = struct { c.LLVMAddIncoming(phi, &phi_vals, &phi_blocks, 2); break :blk phi; } else blk: { - const get_obj_cls = self.loadJniFn(ifs, 31, "jni.GetObjectClass"); + 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, 33, "jni.GetMethodID"); + const get_mid = self.loadJniFn(ifs, Jni.GetMethodID, "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 };