ffi 2.16c xfail: omitted env in #jni_call from cross-function helper

A `#jni_call(void)(target, "name", "sig")` inside a helper fn that
isn't lexically inside a `#jni_env` block should fall back to a
thread-local env read populated by the enclosing `#jni_env(env) {
helper(target); }` scope at runtime. Today the lower-side
"jni_env_stack empty" diagnostic gets queued but compilation
continues to emit_llvm, which fails LLVM verification because env
lowers to `Ref.none` (`i64 undef`).

The make-green follow-up:
- Synthesizes a thread-local `@sx_jni_env_tl` global in emit_llvm.
- `#jni_env(env) { body }` emits a `(load TL → saved, store env → TL,
  defer store saved → TL)` sequence so the TL tracks the
  innermost-scope env and restores correctly on nesting.
- `lowerJniCall`'s omitted-env path falls back to a TL load when
  `jni_env_stack` is empty, instead of erroring.

The lexical-direct optimisation from 2.16b stays the fast path —
helpers in the same fn never touch TL. Only cross-fn callees pay
the (cheap) TL load.
This commit is contained in:
agra
2026-05-20 12:51:48 +03:00
parent 8d1816018a
commit 013cf9f1bb
3 changed files with 52 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
// Phase 2 step 2.16c (PLAN-FFI.md): xfail then green for the
// thread-local env fallback in `#jni_call`. When a `#jni_call` site
// has its env arg omitted AND no `#jni_env` block exists in the same
// function (e.g., we're in a helper called FROM such a block), the
// compiler emits a TL load instead of a sema error.
//
// The TL is pushed/popped by the `#jni_env(env) { ... }` enclosing
// scope at runtime; helpers that don't see the lexical scope still
// pick up the env transparently. Cross-function callers no longer
// need to thread env as an explicit parameter.
//
// Today (2.16b only): lowerJniCall errors when env is omitted and
// jni_env_stack is empty, because TL emission isn't wired yet.
#import "modules/std.sx";
g_should_call : bool = false;
// Helper fn — no `#jni_env` block in scope. Without TL fallback this
// errors because the omitted env can't be resolved.
helper :: (target: *void) {
#jni_call(void)(target, "noop", "()V");
}
unused :: (env: *void, target: *void) {
#jni_env(env) {
helper(target);
}
}
main :: () -> s32 {
if g_should_call {
unused(null, null);
}
print("ok\n");
0;
}