`#jni_call` collapses to a single surface — env is *always* implicit:
either picked up from the lexically-enclosing `#jni_env(env) { ... }`
block's Ref (cheap, register-resident, no TL touch) or from the
runtime's thread-local slot via `sx_jni_env_tl_get()` (one fn call
per dispatch). The explicit-env shape is gone — chess and the
existing tests migrate cleanly by wrapping their helper-fn bodies
in `#jni_env(env) { ... }`.
The TL slot lives outside the user's IR module so the LLVM ORC JIT
can load object files cleanly without `orc_rt` for TLS support:
library/vendors/sx_jni_runtime/sx_jni_env_tl.c:
static _Thread_local void *sx_jni_env_tl_slot;
void *sx_jni_env_tl_get(void) { return sx_jni_env_tl_slot; }
void sx_jni_env_tl_set(void *env) { sx_jni_env_tl_slot = env; }
Linkage:
- sx-the-compiler links the .c file via build.zig so the JIT
process-symbol generator resolves `sx_jni_env_tl_get`/`_set`.
- AOT targets get the same .c file auto-linked via the lowering
pass: when lower touches the TL externs, it sets
`needs_jni_env_tl_runtime`, and `Compilation.lowerToIR` appends a
synthetic `CImportInfo` to `lowering_extra_c_sources` that
`collectCImportSources` merges with user-written ones.
Lowering-side changes:
- `getJniEnvTlFids` lazily declares the two externs (parallel
to `getSelRegisterNameFid`) and flips `needs_jni_env_tl_runtime`.
- `#jni_env(env) { body }` emits save→set→body→restore via three
`call` ops to the externs; the inner body sees env via the
lexical-direct stack.
- `lowerJniCall` resolves env from `jni_env_stack` (top) or the TL
fallback. The explicit-env branch is gone.
- `jni_env_stack_base` tracks per-fn lexical scope so lazy-lowering
a callee doesn't accidentally see the caller's Ref (Refs are only
valid inside one fn's instruction stream).
Test migration (mechanical):
- ffi-jni-call-{01..09}: each helper fn wraps `#jni_call(...)`
bodies in `#jni_env(env) { ... }`. Returning values pass through
the block as an expression — `#jni_env` now also lowers in
expression position.
Verified:
- zig build test + tests/run_examples.sh: 130/130 green.
- tests/cross_compile.sh: 3/3 green.
- Chess APK rebuilt + reinstalled on Pixel. Board renders with
status-bar clearance + info panel intact; no crashes in logcat.
Safe-insets dispatch through `#jni_env` + lexical-direct now
fully exercised end-to-end on real hardware.
33 lines
1.1 KiB
Plaintext
33 lines
1.1 KiB
Plaintext
// Phase 1 step 1.16 (PLAN-FFI.md): two `#jni_call` sites against the
|
|
// same (class, method, sig) tuple. Today each site emits its own
|
|
// `GetObjectClass` + `GetMethodID` call (no caching yet); after 1.17
|
|
// the lowering collapses to one cached `jmethodID` per unique
|
|
// (class, method, sig).
|
|
//
|
|
// Runtime: unreachable. The JNI dispatch is all indirect through
|
|
// `*env` — no external symbol declarations — so the IR loads cleanly
|
|
// into the host JIT, and the dereferences never execute because the
|
|
// guarding `g_should_call` global stays false. A plain `if false`
|
|
// would get constant-folded, taking the `unused_jni` body out of the
|
|
// IR with it; the runtime-readable global keeps the call alive so
|
|
// the JNI lowering is visible to the IR snapshot.
|
|
|
|
#import "modules/std.sx";
|
|
|
|
g_should_call : bool = false;
|
|
|
|
unused_jni :: (env: *void, target: *void) {
|
|
#jni_env(env) {
|
|
#jni_call(void)(target, "noop", "()V");
|
|
#jni_call(void)(target, "noop", "()V");
|
|
}
|
|
}
|
|
|
|
main :: () -> s32 {
|
|
if g_should_call {
|
|
unused_jni(null, null);
|
|
}
|
|
print("ok\n");
|
|
0;
|
|
}
|