From 2f7c99fd11ebabea370adade9bbf5deb407352fd Mon Sep 17 00:00:00 2001 From: agra Date: Wed, 3 Jun 2026 10:42:58 +0300 Subject: [PATCH] refactor(backend): extract JNI slot cache into ffi_ctors.zig (A7.3 slice 2a) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move getOrCreateJniSlots (the cls/methodid slot-cache builder) out of emit_llvm.zig into the FfiCtors backend *LLVMEmitter facade. Behavior-preserving — self.* -> self.e.* only. - FfiCtors gains getOrCreateJniSlots (pub). The jni_slots cache + mangleJniKey stay on LLVMEmitter; mangleJniKey is widened to pub (the facade calls it back, like lazyDeclareCRuntime/emitPrivateCString), and JniSlotPair is widened to pub (the facade returns it; the call site consumes it). 1 call site routed via ffiCtors(). - emitJniConstructor intentionally NOT moved in this slice: it is emission-heavy (resolveRef/mapRef/coerceArg/getRefIRType/extractSlicePtr/loadJniFn/ emitCStringGlobal — 100+ internal callers for the first two), so relocating it would pub-expose the emitter's core value-emission machinery. Consistent with A7.2 keeping emitFieldValueGet in emit_llvm.zig. Pending an explicit decision. Gate: zig build, zig build test, bash tests/run_examples.sh -> 361/0 (JNI anchors 1402/1408/1418/1425 green, no churn). --- src/backend/llvm/ffi_ctors.zig | 33 ++++++++++++++++++++++++++++++ src/ir/emit_llvm.zig | 37 +++------------------------------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/backend/llvm/ffi_ctors.zig b/src/backend/llvm/ffi_ctors.zig index 5bfee0d..56df039 100644 --- a/src/backend/llvm/ffi_ctors.zig +++ b/src/backend/llvm/ffi_ctors.zig @@ -4,6 +4,7 @@ const c = llvm.c; const emit = @import("../../ir/emit_llvm.zig"); const LLVMEmitter = emit.LLVMEmitter; +const JniSlotPair = LLVMEmitter.JniSlotPair; /// Obj-C / JNI runtime-constructor emission (architecture phase A7.3), extracted /// from `LLVMEmitter`. A backend `*LLVMEmitter` facade (field `e`): it builds the @@ -473,4 +474,36 @@ pub const FfiCtors = struct { _ = i32_ty; } + + /// Return `{cls_slot, mid_slot}` global pair for the + /// `(name, sig)` literal — created on first lookup, shared across + /// later `#jni_call` sites with the same literal pair. Both + /// slots are zero-initialized `ptr`; the call-site lowering does + /// lazy population on first dispatch. The cache (`jni_slots`) + + /// `mangleJniKey` stay on `LLVMEmitter`. + pub fn getOrCreateJniSlots(self: FfiCtors, name: []const u8, sig: []const u8) JniSlotPair { + // Compose the key from name + a separator + sig. The separator + // is a byte that can't appear in a JNI method name or signature + // (NUL), so the same key never collides across distinct pairs. + const key = std.fmt.allocPrint(self.e.alloc, "{s}\x00{s}", .{ name, sig }) catch unreachable; + if (self.e.jni_slots.get(key)) |existing| { + self.e.alloc.free(key); + return existing; + } + const mangled = self.e.mangleJniKey(name, sig); + defer self.e.alloc.free(mangled); + const cls_name = std.fmt.allocPrintSentinel(self.e.alloc, "SX_JNI_CLS_{s}", .{mangled}, 0) catch unreachable; + defer self.e.alloc.free(cls_name); + const mid_name = std.fmt.allocPrintSentinel(self.e.alloc, "SX_JNI_MID_{s}", .{mangled}, 0) catch unreachable; + defer self.e.alloc.free(mid_name); + const cls_slot = c.LLVMAddGlobal(self.e.llvm_module, self.e.cached_ptr, cls_name.ptr); + c.LLVMSetLinkage(cls_slot, c.LLVMInternalLinkage); + c.LLVMSetInitializer(cls_slot, c.LLVMConstNull(self.e.cached_ptr)); + const mid_slot = c.LLVMAddGlobal(self.e.llvm_module, self.e.cached_ptr, mid_name.ptr); + c.LLVMSetLinkage(mid_slot, c.LLVMInternalLinkage); + c.LLVMSetInitializer(mid_slot, c.LLVMConstNull(self.e.cached_ptr)); + const pair = JniSlotPair{ .cls_slot = cls_slot, .mid_slot = mid_slot }; + self.e.jni_slots.put(key, pair) catch unreachable; + return pair; + } }; diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index 689324c..3e20c4e 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -221,7 +221,7 @@ pub const LLVMEmitter = struct { param_index: u32, }; - const JniSlotPair = struct { + pub const JniSlotPair = struct { cls_slot: c.LLVMValueRef, // @SX_JNI_CLS_: ptr (GlobalRef to jclass) mid_slot: c.LLVMValueRef, // @SX_JNI_MID_: ptr (jmethodID) }; @@ -623,43 +623,12 @@ pub const LLVMEmitter = struct { } } - /// Return `{cls_slot, mid_slot}` global pair for the - /// `(name, sig)` literal — created on first lookup, shared across - /// later `#jni_call` sites with the same literal pair. Both - /// slots are zero-initialized `ptr`; the call-site lowering does - /// lazy population on first dispatch. - fn getOrCreateJniSlots(self: *LLVMEmitter, name: []const u8, sig: []const u8) JniSlotPair { - // Compose the key from name + a separator + sig. The separator - // is a byte that can't appear in a JNI method name or signature - // (NUL), so the same key never collides across distinct pairs. - const key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}", .{ name, sig }) catch unreachable; - if (self.jni_slots.get(key)) |existing| { - self.alloc.free(key); - return existing; - } - const mangled = self.mangleJniKey(name, sig); - defer self.alloc.free(mangled); - const cls_name = std.fmt.allocPrintSentinel(self.alloc, "SX_JNI_CLS_{s}", .{mangled}, 0) catch unreachable; - defer self.alloc.free(cls_name); - const mid_name = std.fmt.allocPrintSentinel(self.alloc, "SX_JNI_MID_{s}", .{mangled}, 0) catch unreachable; - defer self.alloc.free(mid_name); - const cls_slot = c.LLVMAddGlobal(self.llvm_module, self.cached_ptr, cls_name.ptr); - c.LLVMSetLinkage(cls_slot, c.LLVMInternalLinkage); - c.LLVMSetInitializer(cls_slot, c.LLVMConstNull(self.cached_ptr)); - const mid_slot = c.LLVMAddGlobal(self.llvm_module, self.cached_ptr, mid_name.ptr); - c.LLVMSetLinkage(mid_slot, c.LLVMInternalLinkage); - c.LLVMSetInitializer(mid_slot, c.LLVMConstNull(self.cached_ptr)); - const pair = JniSlotPair{ .cls_slot = cls_slot, .mid_slot = mid_slot }; - self.jni_slots.put(key, pair) catch unreachable; - return pair; - } - /// Build an LLVM-friendly identifier suffix from a JNI /// `(method_name, signature)` pair. Non-identifier characters are /// rewritten to `_`; the resulting string is unique per pair (the /// caller guarantees uniqueness on `(name, sig)`, which we /// preserve through the separator between mangled name and sig). - fn mangleJniKey(self: *LLVMEmitter, name: []const u8, sig: []const u8) []u8 { + pub fn mangleJniKey(self: *LLVMEmitter, name: []const u8, sig: []const u8) []u8 { var buf = std.ArrayList(u8).empty; for (name) |b| buf.append(self.alloc, if (isIdentByte(b)) b else '_') catch unreachable; buf.appendSlice(self.alloc, "__") catch unreachable; @@ -1894,7 +1863,7 @@ pub const LLVMEmitter = struct { // back to the per-call `GetObjectClass + GetMethodID` // sequence (1.15 shape). const mid = if (msg.cache_key) |ck| blk: { - const pair = self.getOrCreateJniSlots(ck.name_str, ck.sig_str); + const pair = self.ffiCtors().getOrCreateJniSlots(ck.name_str, ck.sig_str); const cached_mid = c.LLVMBuildLoad2(self.builder, self.cached_ptr, pair.mid_slot, "jni.cached.mid"); const is_cached = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cached_mid, c.LLVMConstNull(self.cached_ptr), "jni.is.cached");