ffi #jni_main: Alias.new(args) constructor dispatch via JNI NewObject
Adds the constructor-invocation arm of the foreign-class DSL:
`SurfaceView.new(ctx)` (where `SurfaceView` is a `#foreign #jni_class`
with `static new :: (ctx: *Context) -> *Self;`) lowers to
`FindClass(env, "android/view/SurfaceView") + GetMethodID(env, cls,
"<init>", "(args)V") + NewObject(env, cls, mid, args...)`. Returns
the fresh jobject.
- inst.zig: `JniMsgSend.is_constructor` flag + `parent_class_path`
re-purposed to carry the class being constructed (alongside its
existing nonvirtual-super-class use). Mutually exclusive with
`is_static` / `is_nonvirtual`.
- lower.zig: `lowerCall.field_access` arm now recognises
`Alias.method(args)` where `Alias` resolves in `foreign_class_map`
and the matching member is `static`. `new` routes to a new
`lowerForeignStaticCall` that derives a `(args)V` JNI descriptor
and emits a `JniMsgSend` with `is_constructor=true`. Non-`new`
static calls report a clear "use #jni_static_call" diagnostic
until that sugar lands.
- emit_llvm.zig: new `NewObject` vtable slot (28) + `emitJniConstructor`
helper expanding the FindClass+GetMethodID+NewObject chain. The
jni_msg_send arm short-circuits to it when `is_constructor` is set.
Smoke `ffi-jni-main-03-ctor.sx` exercises both this slice and the
previous super-dispatch slice in a single `onCreate` body: calls
`super.onCreate(b)` then constructs a `SurfaceView` with the Activity
as Context. IR shows the expected six-stage chain (FindClass+GetMethodID+
CallNonvirtual + FindClass+GetMethodID+NewObject); APK builds clean.
Naming caveat: the Java type `android.content.Context` clashes with
sx stdlib's `Context :: struct {...}` (heap-context). The smoke aliases
it `JContext` — future work could add a path-prefix or `as` rename
form on `#jni_class` to avoid the manual rename.
133 host / 6 cross / zig build test all green.
This commit is contained in:
@@ -37,6 +37,7 @@ fn isIdentByte(b: u8) bool {
|
||||
const Jni = struct {
|
||||
const FindClass: u32 = 6;
|
||||
const NewGlobalRef: u32 = 21;
|
||||
const NewObject: u32 = 28;
|
||||
const GetObjectClass: u32 = 31;
|
||||
const GetMethodID: u32 = 33;
|
||||
// Call<Type>Method (instance, varargs variant). Each numeric type
|
||||
@@ -1264,8 +1265,17 @@ pub const LLVMEmitter = struct {
|
||||
// static: target IS the jclass — skip GetObjectClass
|
||||
// mid = ifs[GetStaticMethodID](env, target, name, sig)
|
||||
// ifs[CallStatic<T>Method](env, target, mid, args...)
|
||||
// ctor: cls = ifs[FindClass](env, parent_class_path)
|
||||
// mid = ifs[GetMethodID](env, cls, "<init>", sig)
|
||||
// ifs[NewObject](env, cls, mid, args...) → jobject
|
||||
// nonvirt: handled below via FindClass + GetMethodID +
|
||||
// CallNonvirtual<T>Method.
|
||||
// The cached path (msg.cache_key != null) still shares one
|
||||
// (jclass GlobalRef, jmethodID) pair per literal (name, sig).
|
||||
if (msg.is_constructor) {
|
||||
self.emitJniConstructor(msg, instruction.ty);
|
||||
return;
|
||||
}
|
||||
const ret_ty_id = instruction.ty;
|
||||
const is_pointer_ret = switch (self.ir_mod.types.get(ret_ty_id)) {
|
||||
.pointer, .many_pointer => true,
|
||||
@@ -3640,6 +3650,56 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMBuildGlobalStringPtr(self.builder, z.ptr, name);
|
||||
}
|
||||
|
||||
/// Expand a JNI constructor dispatch (`Foo.new(args)` in sx). Chain:
|
||||
/// `FindClass(env, parent_class_path)` → `GetMethodID(env, clazz,
|
||||
/// "<init>", sig)` → `NewObject(env, clazz, mid, args...)`. Returns
|
||||
/// the new jobject. Per-call lookups — no caching yet.
|
||||
fn emitJniConstructor(self: *LLVMEmitter, msg: ir_inst.JniMsgSend, ret_ty_id: TypeId) void {
|
||||
const env = self.resolveRef(msg.env);
|
||||
const sig_ptr = self.extractSlicePtr(self.resolveRef(msg.sig));
|
||||
const name_ptr = self.extractSlicePtr(self.resolveRef(msg.name));
|
||||
|
||||
const ifs = c.LLVMBuildLoad2(self.builder, self.cached_ptr, env, "jni.ifs");
|
||||
|
||||
const path = msg.parent_class_path orelse "";
|
||||
const path_global = self.emitCStringGlobal(path, "jni.ctor.path");
|
||||
const find_class = self.loadJniFn(ifs, Jni.FindClass, "jni.FindClass");
|
||||
var fc_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr };
|
||||
const fc_ty = c.LLVMFunctionType(self.cached_ptr, &fc_params, 2, 0);
|
||||
var fc_args = [_]c.LLVMValueRef{ env, path_global };
|
||||
const cls = c.LLVMBuildCall2(self.builder, fc_ty, find_class, &fc_args, 2, "jni.ctor.cls");
|
||||
|
||||
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 };
|
||||
const mid = c.LLVMBuildCall2(self.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.ctor.mid");
|
||||
|
||||
const new_object = self.loadJniFn(ifs, Jni.NewObject, "jni.NewObject");
|
||||
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] = cls;
|
||||
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 result = c.LLVMBuildCall2(self.builder, call_fn_ty, new_object, call_args.ptr, @intCast(total_call_params), "jni.new.obj");
|
||||
self.mapRef(result);
|
||||
}
|
||||
|
||||
// ── Reflection emission helpers ────────────────────────────────
|
||||
|
||||
/// Build (or return cached) a global constant array of {ptr, i64} string values
|
||||
|
||||
Reference in New Issue
Block a user