ffi 2.16c green: TL fallback via C-helper runtime + always-omit env in #jni_call

`#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.
This commit is contained in:
agra
2026-05-20 13:53:25 +03:00
parent 013cf9f1bb
commit 6a3260ff65
26 changed files with 330 additions and 96 deletions

View File

@@ -208,6 +208,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_noop____V, align 8
@@ -233,32 +235,32 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 61
%jni.callfn = load ptr, ptr %5, align 8
call void %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
%loadN = load ptr, ptr %alloca, align 8
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs5 = load ptr, ptr %loadN, align 8
%jni.cached.mid6 = load ptr, ptr @SX_JNI_MID_noop____V, align 8
%jni.is.cached7 = icmp ne ptr %jni.cached.mid6, null
br i1 %jni.is.cached7, label %jni.cont9, label %jni.miss8
%jni.ifs4 = load ptr, ptr %load, align 8
%jni.cached.mid5 = load ptr, ptr @SX_JNI_MID_noop____V, align 8
%jni.is.cached6 = icmp ne ptr %jni.cached.mid5, null
br i1 %jni.is.cached6, label %jni.cont8, label %jni.miss7
jni.miss8: ; preds = %jni.cont
%6 = getelementptr inbounds ptr, ptr %jni.ifs5, i32 31
%jni.GetObjectClass10 = load ptr, ptr %6, align 8
%jni.cls11 = call ptr %jni.GetObjectClass10(ptr %loadN, ptr %loadN)
%7 = getelementptr inbounds ptr, ptr %jni.ifs5, i32 21
%jni.NewGlobalRef12 = load ptr, ptr %7, align 8
%jni.global.cls13 = call ptr %jni.NewGlobalRef12(ptr %loadN, ptr %jni.cls11)
store ptr %jni.global.cls13, ptr @SX_JNI_CLS_noop____V, align 8
%8 = getelementptr inbounds ptr, ptr %jni.ifs5, i32 33
%jni.GetMethodID14 = load ptr, ptr %8, align 8
%jni.fresh.mid15 = call ptr %jni.GetMethodID14(ptr %loadN, ptr %jni.global.cls13, ptr @str.2, ptr @str.3)
store ptr %jni.fresh.mid15, ptr @SX_JNI_MID_noop____V, align 8
br label %jni.cont9
jni.miss7: ; preds = %jni.cont
%6 = getelementptr inbounds ptr, ptr %jni.ifs4, i32 31
%jni.GetObjectClass9 = load ptr, ptr %6, align 8
%jni.cls10 = call ptr %jni.GetObjectClass9(ptr %load, ptr %loadN)
%7 = getelementptr inbounds ptr, ptr %jni.ifs4, i32 21
%jni.NewGlobalRef11 = load ptr, ptr %7, align 8
%jni.global.cls12 = call ptr %jni.NewGlobalRef11(ptr %load, ptr %jni.cls10)
store ptr %jni.global.cls12, ptr @SX_JNI_CLS_noop____V, align 8
%8 = getelementptr inbounds ptr, ptr %jni.ifs4, i32 33
%jni.GetMethodID13 = load ptr, ptr %8, align 8
%jni.fresh.mid14 = call ptr %jni.GetMethodID13(ptr %load, ptr %jni.global.cls12, ptr @str.2, ptr @str.3)
store ptr %jni.fresh.mid14, ptr @SX_JNI_MID_noop____V, align 8
br label %jni.cont8
jni.cont9: ; preds = %jni.miss8, %jni.cont
%jni.mid16 = phi ptr [ %jni.cached.mid6, %jni.cont ], [ %jni.fresh.mid15, %jni.miss8 ]
%9 = getelementptr inbounds ptr, ptr %jni.ifs5, i32 61
%jni.callfn17 = load ptr, ptr %9, align 8
call void %jni.callfn17(ptr %loadN, ptr %loadN, ptr %jni.mid16)
jni.cont8: ; preds = %jni.miss7, %jni.cont
%jni.mid15 = phi ptr [ %jni.cached.mid5, %jni.cont ], [ %jni.fresh.mid14, %jni.miss7 ]
%9 = getelementptr inbounds ptr, ptr %jni.ifs4, i32 61
%jni.callfn16 = load ptr, ptr %9, align 8
call void %jni.callfn16(ptr %load, ptr %loadN, ptr %jni.mid15)
call void @sx_jni_env_tl_set(ptr %call)
ret void
}
@@ -316,6 +318,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_getCount____I, align 8
@@ -231,6 +233,7 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 49
%jni.callfn = load ptr, ptr %5, align 8
%jni.ret = call i32 %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
call void @sx_jni_env_tl_set(ptr %call)
ret i32 %jni.ret
}
@@ -290,6 +293,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_currentTimeMillis____J, align 8
@@ -231,6 +233,7 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 52
%jni.callfn = load ptr, ptr %5, align 8
%jni.ret = call i64 %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
call void @sx_jni_env_tl_set(ptr %call)
ret i64 %jni.ret
}
@@ -290,6 +293,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_getValue____D, align 8
@@ -231,6 +233,7 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 58
%jni.callfn = load ptr, ptr %5, align 8
%jni.ret = call double %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
call void @sx_jni_env_tl_set(ptr %call)
ret double %jni.ret
}
@@ -290,6 +293,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_isShown____Z, align 8
@@ -231,6 +233,7 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 37
%jni.callfn = load ptr, ptr %5, align 8
%jni.ret = call i1 %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
call void @sx_jni_env_tl_set(ptr %call)
ret i1 %jni.ret
}
@@ -290,6 +293,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_getWindow____Landroid_view_Window_, align 8
@@ -231,6 +233,7 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 34
%jni.callfn = load ptr, ptr %5, align 8
%jni.ret = call ptr %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
call void @sx_jni_env_tl_set(ptr %call)
ret ptr %jni.ret
}
@@ -290,6 +293,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_max___II_I, align 8
@@ -228,6 +230,7 @@ jni.cont: ; preds = %jni.miss, %entry
%4 = getelementptr inbounds ptr, ptr %jni.ifs, i32 129
%jni.callfn = load ptr, ptr %4, align 8
%jni.ret = call i32 %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid, i32 3, i32 7)
call void @sx_jni_env_tl_set(ptr %call)
ret i32 %jni.ret
}
@@ -287,6 +290,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_getWindow____Ljava_lang_Object_, align 8
@@ -233,6 +235,7 @@ jni.cont: ; preds = %jni.miss, %entry
%jni.ret = call ptr %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
%allocaN = alloca ptr, align 8
store ptr %jni.ret, ptr %allocaN, align 8
call void @sx_jni_env_tl_set(ptr %call)
ret void
}
@@ -290,4 +293,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -206,6 +206,8 @@ entry:
%allocaN = alloca ptr, align 8
store ptr %1, ptr %allocaN, align 8
%load = load ptr, ptr %alloca, align 8
%call = call ptr @sx_jni_env_tl_get()
call void @sx_jni_env_tl_set(ptr %load)
%loadN = load ptr, ptr %allocaN, align 8
%jni.ifs = load ptr, ptr %load, align 8
%jni.cached.mid = load ptr, ptr @SX_JNI_MID_noop____V, align 8
@@ -231,6 +233,7 @@ jni.cont: ; preds = %jni.miss, %entry
%5 = getelementptr inbounds ptr, ptr %jni.ifs, i32 61
%jni.callfn = load ptr, ptr %5, align 8
call void %jni.callfn(ptr %load, ptr %loadN, ptr %jni.mid)
call void @sx_jni_env_tl_set(ptr %call)
ret void
}
@@ -288,4 +291,10 @@ entry:
ret void
}
; Function Attrs: nounwind
declare ptr @sx_jni_env_tl_get() #0
; Function Attrs: nounwind
declare void @sx_jni_env_tl_set(ptr) #0
declare i64 @write(i32, ptr, i64)

View File

@@ -1 +1 @@
1
0

View File

@@ -1,14 +1 @@
LLVM verification failed: Load operand must be a pointer.
%jni.ifs = load ptr, i64 undef, align 8
Call parameter type does not match function signature!
i64 undef
ptr %jni.cls = call ptr %jni.GetObjectClass(i64 undef, ptr %load)
Call parameter type does not match function signature!
i64 undef
ptr %jni.global.cls = call ptr %jni.NewGlobalRef(i64 undef, ptr %jni.cls)
Call parameter type does not match function signature!
i64 undef
ptr %jni.fresh.mid = call ptr %jni.GetMethodID(i64 undef, ptr %jni.global.cls, ptr @str, ptr @str.1)
Call parameter type does not match function signature!
i64 undef
ptr call void %jni.callfn(i64 undef, ptr %load, ptr %jni.mid)
ok