ffi 1.15: #jni_call(void) codegen — make-green
New `.jni_msg_send` IR opcode carrying `{env, target, name, sig,
args[], is_static}`. `lowerFfiIntrinsicCall` now dispatches on
`fic.kind`: `.objc_call` keeps the existing path; `.jni_call` and
`.jni_static_call` route through `lowerJniCall`, which emits the new
opcode.
emit_llvm.zig expands `.jni_msg_send` into the JNI vtable
indirection:
%ifs = load ptr, %env ; vtable
%get_obj_class = load ptr, gep(%ifs, i32 31)
%cls = call ptr %get_obj_class(%env, %target)
%get_method_id = load ptr, gep(%ifs, i32 33)
%mid = call ptr %get_method_id(%env, %cls, %name, %sig)
%call_void_method = load ptr, gep(%ifs, i32 61)
call void %call_void_method(%env, %target, %mid, args...)
Per step 1.15's scope: only `.jni_call` (instance) + `void` return
are wired through the switch. `.jni_static_call` (1.23) and the
non-void returns (1.18–1.22) drop to a placeholder `LLVMGetUndef` so
the build doesn't fault — the next-step commits flip those arms one
shape at a time. Method-ID caching is step 1.17.
Two small helpers landed alongside:
- `loadJniFn(ifs, offset, name)` — GEP into the vtable + load.
- `extractSlicePtr(val)` — string literals lower as `{ptr, i64}`
slices in sx IR; JNI's `GetMethodID` expects raw C strings, so
this extracts field 0 when the source is a slice.
Android cross-compile now passes for `examples/ffi-jni-call-02-void.sx`
(2/2 cross targets green). Host run_examples still passes 112/112.
Chess iOS-sim + Android both compile clean.
This commit is contained in:
@@ -322,6 +322,29 @@ pub const LLVMEmitter = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// If `val` is a `{ptr, i64}` slice struct, extract field 0
|
||||
/// (the ptr); otherwise return it unchanged. Used by JNI dispatch
|
||||
/// to feed string-literal method names + signatures to
|
||||
/// `GetMethodID`, which expects raw C strings.
|
||||
fn extractSlicePtr(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
|
||||
const val_ty = c.LLVMTypeOf(val);
|
||||
if (c.LLVMGetTypeKind(val_ty) != c.LLVMStructTypeKind) return val;
|
||||
if (c.LLVMCountStructElementTypes(val_ty) != 2) return val;
|
||||
const f0 = c.LLVMStructGetTypeAtIndex(val_ty, 0);
|
||||
if (c.LLVMGetTypeKind(f0) != c.LLVMPointerTypeKind) return val;
|
||||
return c.LLVMBuildExtractValue(self.builder, val, 0, "jni.str.ptr");
|
||||
}
|
||||
|
||||
/// Load a JNI vtable function pointer at the given offset. `ifs`
|
||||
/// is the `JNINativeInterface*` loaded from `JNIEnv*`. Treats the
|
||||
/// vtable as an array of opaque `ptr`s and indexes into it.
|
||||
fn loadJniFn(self: *LLVMEmitter, ifs: c.LLVMValueRef, offset: u32, name: [*:0]const u8) c.LLVMValueRef {
|
||||
const offset_val = c.LLVMConstInt(self.cached_i32, offset, 0);
|
||||
var idx = [_]c.LLVMValueRef{offset_val};
|
||||
const slot = c.LLVMBuildInBoundsGEP2(self.builder, self.cached_ptr, ifs, &idx, 1, "");
|
||||
return c.LLVMBuildLoad2(self.builder, self.cached_ptr, slot, name);
|
||||
}
|
||||
|
||||
/// Lazily look up / declare the shared `@objc_msgSend` function.
|
||||
/// Cached on the emitter; all `objc_msg_send` instructions hand
|
||||
/// LLVMBuildCall2 their own per-call-site function type — the
|
||||
@@ -1120,6 +1143,77 @@ pub const LLVMEmitter = struct {
|
||||
// ref lookup in this function.
|
||||
self.mapRef(result);
|
||||
},
|
||||
.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;
|
||||
}
|
||||
const ret_ty_id = instruction.ty;
|
||||
const call_method_offset: u32 = switch (ret_ty_id) {
|
||||
.void => 61, // CallVoidMethod
|
||||
else => {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
const env = self.resolveRef(msg.env);
|
||||
const target = self.resolveRef(msg.target);
|
||||
// String literals lower as `{ptr, i64}` slices in sx IR;
|
||||
// JNI's `GetMethodID` expects raw C strings, so extract
|
||||
// field 0 when the source is a slice.
|
||||
const name_ptr = self.extractSlicePtr(self.resolveRef(msg.name));
|
||||
const sig_ptr = self.extractSlicePtr(self.resolveRef(msg.sig));
|
||||
|
||||
const ifs = c.LLVMBuildLoad2(self.builder, self.cached_ptr, env, "jni.ifs");
|
||||
|
||||
// GetObjectClass: (JNIEnv*, jobject) -> jclass
|
||||
const get_obj_cls = self.loadJniFn(ifs, 31, "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");
|
||||
|
||||
// GetMethodID: (JNIEnv*, jclass, const char*, const char*) -> jmethodID
|
||||
const get_mid = self.loadJniFn(ifs, 33, "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 };
|
||||
const mid = c.LLVMBuildCall2(self.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.mid");
|
||||
|
||||
// Call<Type>Method: (JNIEnv*, jobject, jmethodID, args...) -> RetTy
|
||||
const call_fn = self.loadJniFn(ifs, call_method_offset, "jni.callfn");
|
||||
const raw_ret = self.toLLVMType(ret_ty_id);
|
||||
const total_call_params: usize = 3 + msg.args.len;
|
||||
const call_param_types = self.alloc.alloc(c.LLVMTypeRef, total_call_params) catch unreachable;
|
||||
defer self.alloc.free(call_param_types);
|
||||
const call_args = self.alloc.alloc(c.LLVMValueRef, total_call_params) catch unreachable;
|
||||
defer self.alloc.free(call_args);
|
||||
call_param_types[0] = self.cached_ptr;
|
||||
call_param_types[1] = self.cached_ptr;
|
||||
call_param_types[2] = self.cached_ptr;
|
||||
call_args[0] = env;
|
||||
call_args[1] = target;
|
||||
call_args[2] = mid;
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
call_param_types[i + 3] = coerced_ty;
|
||||
call_args[i + 3] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
const call_fn_ty = c.LLVMFunctionType(raw_ret, call_param_types.ptr, @intCast(total_call_params), 0);
|
||||
const label: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.ret";
|
||||
const result = c.LLVMBuildCall2(self.builder, call_fn_ty, call_fn, call_args.ptr, @intCast(total_call_params), label);
|
||||
self.mapRef(result);
|
||||
},
|
||||
.call => |call_op| {
|
||||
// Evaluate comptime functions at compile time
|
||||
const callee_func = &self.ir_mod.functions.items[call_op.callee.index()];
|
||||
|
||||
Reference in New Issue
Block a user