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:
agra
2026-05-19 21:32:18 +03:00
parent 134c197dd4
commit 9afcaa5af0
5 changed files with 159 additions and 5 deletions

View File

@@ -3790,11 +3790,8 @@ pub const Lowering = struct {
/// fully wired. Extra arities + non-void returns will land in
/// subsequent phase-1 steps.
fn lowerFfiIntrinsicCall(self: *Lowering, fic: *const ast.FfiIntrinsicCall) Ref {
if (fic.kind != .objc_call) {
if (self.diagnostics) |d| {
d.add(.err, "#jni_call / #jni_static_call lowering not implemented yet (Phase 1.15+)", null);
}
return Ref.none;
if (fic.kind == .jni_call or fic.kind == .jni_static_call) {
return self.lowerJniCall(fic);
}
if (fic.args.len < 2) {
@@ -3855,6 +3852,37 @@ pub const Lowering = struct {
} }, ret_ty);
}
fn lowerJniCall(self: *Lowering, fic: *const ast.FfiIntrinsicCall) Ref {
if (fic.args.len < 4) {
if (self.diagnostics) |d| {
d.add(.err, "#jni_call requires env, target, method name, and signature", null);
}
return Ref.none;
}
const ret_ty = self.resolveType(fic.return_type);
const env_ref = self.lowerExpr(fic.args[0]);
const target_ref = self.lowerExpr(fic.args[1]);
const name_ref = self.lowerExpr(fic.args[2]);
const sig_ref = self.lowerExpr(fic.args[3]);
var extra = std.ArrayList(Ref).empty;
var ai: usize = 4;
while (ai < fic.args.len) : (ai += 1) {
extra.append(self.alloc, self.lowerExpr(fic.args[ai])) catch unreachable;
}
const extra_owned = extra.toOwnedSlice(self.alloc) catch unreachable;
return self.builder.emit(.{ .jni_msg_send = .{
.env = env_ref,
.target = target_ref,
.name = name_ref,
.sig = sig_ref,
.args = extra_owned,
.is_static = fic.kind == .jni_static_call,
} }, ret_ty);
}
// ── Calls ───────────────────────────────────────────────────────
fn lowerCall(self: *Lowering, c: *const ast.Call) Ref {