ffi 1.23: #jni_static_call lowering — make-green
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 `CallStatic<Type>Method` (Object 114 / Boolean 117
/ Int 129 / Long 132 / Float 135 / Double 138 / Void 141).
Slot interning still applies: the `@SX_JNI_{CLS,MID}_<key>` 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.
This commit is contained in:
@@ -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; `CallStatic<Type>Method` 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[Call<T>Method](env, target, mid, args...)
|
||||
// static: target IS the jclass — skip GetObjectClass
|
||||
// mid = ifs[GetStaticMethodID](env, target, name, sig)
|
||||
// ifs[CallStatic<T>Method](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 };
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user