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:
@@ -27,6 +27,15 @@ pub fn build(b: *std.Build) void {
|
||||
.file = b.path("llvm_shim.c"),
|
||||
.flags = &.{b.fmt("-I{s}", .{include_dir})},
|
||||
});
|
||||
// FFI step 2.16c runtime — `_Thread_local`-backed JNIEnv* slot.
|
||||
// Linked into sx-the-compiler so the JIT process-symbol generator
|
||||
// can resolve `sx_jni_env_tl_get` / `sx_jni_env_tl_set` without the
|
||||
// user importing the runtime module. AOT outputs pick up the same
|
||||
// .c file via lower-side auto-injected c_import.
|
||||
mod.addCSourceFile(.{
|
||||
.file = b.path("library/vendors/sx_jni_runtime/sx_jni_env_tl.c"),
|
||||
.flags = &.{},
|
||||
});
|
||||
mod.addCSourceFile(.{
|
||||
.file = b.path("clang_shim.cpp"),
|
||||
.flags = &.{
|
||||
|
||||
@@ -10,14 +10,17 @@
|
||||
|
||||
main :: () -> s32 {
|
||||
inline if false {
|
||||
// Instance method: env, target, name, sig, args...
|
||||
#jni_call(*void)(null, null, "getWindow", "()Landroid/view/Window;");
|
||||
env : *void = null;
|
||||
#jni_env(env) {
|
||||
// Instance method: target, name, sig, args...
|
||||
#jni_call(*void)(null, "getWindow", "()Landroid/view/Window;");
|
||||
|
||||
// Static method: env, class, name, sig, args...
|
||||
#jni_static_call(s32)(null, null, "max", "(II)I", 3, 7);
|
||||
// Static method: class, name, sig, args...
|
||||
#jni_static_call(s32)(null, "max", "(II)I", 3, 7);
|
||||
|
||||
// Returning a Java primitive (jboolean → sx bool).
|
||||
#jni_call(bool)(null, null, "isShown", "()Z");
|
||||
// Returning a Java primitive (jboolean → sx bool).
|
||||
#jni_call(bool)(null, "isShown", "()Z");
|
||||
}
|
||||
}
|
||||
print("parse-only ok\n");
|
||||
0;
|
||||
|
||||
@@ -31,7 +31,9 @@ main :: () -> s32 {
|
||||
// sx_android_jni.c lands.
|
||||
env : *void = null;
|
||||
target : *void = null;
|
||||
#jni_call(void)(env, target, "noop", "()V");
|
||||
#jni_env(env) {
|
||||
#jni_call(void)(target, "noop", "()V");
|
||||
}
|
||||
}
|
||||
inline if OS != .android {
|
||||
print("skipped (not android)\n");
|
||||
|
||||
@@ -17,8 +17,10 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
unused_jni :: (env: *void, target: *void) {
|
||||
#jni_call(void)(env, target, "noop", "()V");
|
||||
#jni_call(void)(env, target, "noop", "()V");
|
||||
#jni_env(env) {
|
||||
#jni_call(void)(target, "noop", "()V");
|
||||
#jni_call(void)(target, "noop", "()V");
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
read_int :: (env: *void, target: *void) -> s32 {
|
||||
#jni_call(s32)(env, target, "getCount", "()I");
|
||||
#jni_env(env) {
|
||||
#jni_call(s32)(target, "getCount", "()I");
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
read_long :: (env: *void, target: *void) -> s64 {
|
||||
#jni_call(s64)(env, target, "currentTimeMillis", "()J");
|
||||
#jni_env(env) {
|
||||
#jni_call(s64)(target, "currentTimeMillis", "()J");
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
read_double :: (env: *void, target: *void) -> f64 {
|
||||
#jni_call(f64)(env, target, "getValue", "()D");
|
||||
#jni_env(env) {
|
||||
#jni_call(f64)(target, "getValue", "()D");
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
read_bool :: (env: *void, target: *void) -> bool {
|
||||
#jni_call(bool)(env, target, "isShown", "()Z");
|
||||
#jni_env(env) {
|
||||
#jni_call(bool)(target, "isShown", "()Z");
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
get_window :: (env: *void, activity: *void) -> *void {
|
||||
#jni_call(*void)(env, activity, "getWindow", "()Landroid/view/Window;");
|
||||
#jni_env(env) {
|
||||
#jni_call(*void)(activity, "getWindow", "()Landroid/view/Window;");
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
g_should_call : bool = false;
|
||||
|
||||
call_static_max :: (env: *void, cls: *void) -> s32 {
|
||||
#jni_static_call(s32)(env, cls, "max", "(II)I", 3, 7);
|
||||
#jni_env(env) {
|
||||
#jni_static_call(s32)(cls, "max", "(II)I", 3, 7);
|
||||
}
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
|
||||
30
library/vendors/sx_jni_runtime/sx_jni_env_tl.c
vendored
Normal file
30
library/vendors/sx_jni_runtime/sx_jni_env_tl.c
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
// Thread-local JNIEnv* slot for the `#jni_env(env) { body }` block and
|
||||
// the `#jni_call` cross-function fallback (FFI plan step 2.16c).
|
||||
//
|
||||
// Lives outside the user's IR module on purpose. The natural place
|
||||
// would be `@sx_jni_env_tl = internal thread_local global ptr null`
|
||||
// inside the lowered IR, but LLVM ORC JIT's default platform support
|
||||
// doesn't initialise TLS slots for objects added via
|
||||
// `LLVMOrcLLJITAddObjectFile`. Wrapping the storage in an externally-
|
||||
// linked C helper sidesteps that — JIT process-symbol resolution finds
|
||||
// these `_Thread_local`-backed functions via the host's dlsym (sx
|
||||
// itself is built with this .c linked in via build.zig); AOT targets
|
||||
// (Android, etc.) pick it up as a regular `#import c { #source ...; }`
|
||||
// auto-injected by the lowering pass.
|
||||
//
|
||||
// The slot is per-thread; nesting is handled at the call site via
|
||||
// save → set(new) → body → set(saved) (see lower.zig). Multi-VM
|
||||
// nesting needs the caller to track that themselves — the slot
|
||||
// doesn't know which JVM the env belongs to.
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
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;
|
||||
}
|
||||
51
src/core.zig
51
src/core.zig
@@ -28,6 +28,10 @@ pub const Compilation = struct {
|
||||
import_graph: std.StringHashMap(std.StringHashMap(void)),
|
||||
sema_result: ?sema.SemaResult = null,
|
||||
ir_emitter: ?ir.LLVMEmitter = null,
|
||||
/// C sources requested by the lowering pass (not in the user's AST).
|
||||
/// E.g. the JNI env TL runtime when `#jni_env` is used. Merged with
|
||||
/// AST sources in `collectCImportSources`.
|
||||
lowering_extra_c_sources: std.ArrayList(c_import.CImportInfo) = .empty,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, io: std.Io, file_path: []const u8, source: [:0]const u8, target_config: TargetConfig, stdlib_paths: []const []const u8) Compilation {
|
||||
return .{
|
||||
@@ -145,10 +149,36 @@ pub const Compilation = struct {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Collect C import source info from the resolved AST.
|
||||
/// Collect C import source info — both from user-written `#import c { ... }`
|
||||
/// blocks in the AST AND from lowering-time auto-injections (currently:
|
||||
/// the JNI env TL runtime when `#jni_env` / `#jni_call`-with-omitted-env
|
||||
/// is used). The lower-side auto-injections live in
|
||||
/// `lowering_extra_c_sources` and are populated by `lowerToIR` based on
|
||||
/// `Lowering.needs_jni_env_tl_runtime` etc.
|
||||
pub fn collectCImportSources(self: *Compilation) ![]c_import.CImportInfo {
|
||||
const root = self.resolved_root orelse self.root orelse return &.{};
|
||||
return c_import.collectCImportSources(self.allocator, root);
|
||||
const ast_sources = try c_import.collectCImportSources(self.allocator, root);
|
||||
if (self.lowering_extra_c_sources.items.len == 0) return ast_sources;
|
||||
var merged = std.ArrayList(c_import.CImportInfo).empty;
|
||||
try merged.appendSlice(self.allocator, ast_sources);
|
||||
try merged.appendSlice(self.allocator, self.lowering_extra_c_sources.items);
|
||||
return merged.toOwnedSlice(self.allocator);
|
||||
}
|
||||
|
||||
/// Resolve a stdlib-relative path through the configured `stdlib_paths`.
|
||||
/// Returns the first candidate whose absolute path resolves to an
|
||||
/// existing file. Used by lower-side auto-injected C sources.
|
||||
fn resolveStdlibPath(self: *Compilation, rel: []const u8) !?[]const u8 {
|
||||
for (self.stdlib_paths) |root_path| {
|
||||
const candidate = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ root_path, rel });
|
||||
if (std.Io.Dir.readFileAlloc(.cwd(), self.io, candidate, self.allocator, .limited(1024 * 1024))) |buf| {
|
||||
self.allocator.free(buf);
|
||||
return candidate;
|
||||
} else |_| {
|
||||
self.allocator.free(candidate);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Lower the parsed AST to the sx IR module (shadow pipeline).
|
||||
@@ -168,6 +198,23 @@ pub const Compilation = struct {
|
||||
lowering.import_graph = &self.import_graph;
|
||||
lowering.lowerRoot(root);
|
||||
if (self.diagnostics.hasErrors()) return error.CompileError;
|
||||
|
||||
// Auto-link the JNI env TL runtime when lowering used it. The .c file
|
||||
// ships with the sx library; we resolve it through stdlib_paths so
|
||||
// consumers don't need to vendor a copy.
|
||||
if (lowering.needs_jni_env_tl_runtime) {
|
||||
if (try self.resolveStdlibPath("vendors/sx_jni_runtime/sx_jni_env_tl.c")) |abs_path| {
|
||||
var sources = std.ArrayList([]const u8).empty;
|
||||
try sources.append(self.allocator, abs_path);
|
||||
try self.lowering_extra_c_sources.append(self.allocator, .{
|
||||
.sources = try sources.toOwnedSlice(self.allocator),
|
||||
.includes = &.{},
|
||||
.defines = &.{},
|
||||
.flags = &.{},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
|
||||
@@ -534,6 +534,10 @@ pub const LLVMEmitter = struct {
|
||||
|
||||
c.LLVMSetLinkage(llvm_global, c.LLVMInternalLinkage);
|
||||
|
||||
if (global.is_thread_local) {
|
||||
c.LLVMSetThreadLocal(llvm_global, 1);
|
||||
}
|
||||
|
||||
// Evaluate comptime initializer if present
|
||||
if (global.comptime_func) |func_id| {
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
|
||||
@@ -503,6 +503,10 @@ pub const Global = struct {
|
||||
init_val: ?ConstantValue = null,
|
||||
is_extern: bool = false,
|
||||
is_const: bool = false,
|
||||
/// Thread-local storage. `global_get` / `global_set` emit normal LLVM
|
||||
/// load/store instructions; LLVM handles the per-thread access through
|
||||
/// the `thread_local` attribute on the global.
|
||||
is_thread_local: bool = false,
|
||||
/// For comptime globals: the function to interpret to get the init value.
|
||||
comptime_func: ?FuncId = null,
|
||||
};
|
||||
|
||||
122
src/ir/lower.zig
122
src/ir/lower.zig
@@ -98,6 +98,10 @@ pub const Lowering = struct {
|
||||
current_source_file: ?[]const u8 = null, // source file of function currently being lowered
|
||||
sel_register_name_fid: ?FuncId = null, // lazily-declared `sel_registerName` extern (non-literal selector fallback)
|
||||
jni_env_stack: std.ArrayList(Ref) = std.ArrayList(Ref).empty, // lexical `#jni_env(env)` Ref stack — top is current scope's env for omitted-env `#jni_call`
|
||||
jni_env_stack_base: usize = 0, // index above which the currently-lowering fn's `#jni_env` scopes live; outer-fn Refs aren't valid in this fn's instruction stream
|
||||
jni_env_tl_get_fid: ?FuncId = null, // extern `sx_jni_env_tl_get` (from library/vendors/sx_jni_runtime/sx_jni_env_tl.c)
|
||||
jni_env_tl_set_fid: ?FuncId = null, // extern `sx_jni_env_tl_set`
|
||||
needs_jni_env_tl_runtime: bool = false, // set when lowering touches the JNI env TL; signals Compilation to auto-link the runtime .c
|
||||
foreign_class_map: std.StringHashMap(*const ast.ForeignClassDecl) = std.StringHashMap(*const ast.ForeignClassDecl).init(std.heap.page_allocator), // sx alias → ForeignClassDecl (jni_class / objc_class / swift_class / ... — registered in scan pass)
|
||||
type_bindings: ?std.StringHashMap(TypeId) = null, // generic type param bindings ($T → concrete TypeId)
|
||||
current_match_tags: ?[]const u64 = null, // type tags for current match arm (for runtime dispatch)
|
||||
@@ -736,6 +740,14 @@ pub const Lowering = struct {
|
||||
const saved_block_terminated = self.block_terminated;
|
||||
const saved_force_block_value = self.force_block_value;
|
||||
const saved_source_file = self.current_source_file;
|
||||
// The `#jni_env` Ref stack is lexical within ONE function's instruction
|
||||
// stream — Refs from the caller don't dereference correctly in this
|
||||
// callee's body. Move the visible base to the current top so
|
||||
// omitted-env `#jni_call` in this fn doesn't accidentally pick up the
|
||||
// caller's Refs. Defer covers all the early-return paths below.
|
||||
const saved_jni_env_base = self.jni_env_stack_base;
|
||||
self.jni_env_stack_base = self.jni_env_stack.items.len;
|
||||
defer self.jni_env_stack_base = saved_jni_env_base;
|
||||
self.func_defer_base = self.defer_stack.items.len;
|
||||
self.block_terminated = false;
|
||||
self.force_block_value = false;
|
||||
@@ -1062,15 +1074,27 @@ pub const Lowering = struct {
|
||||
.insert_expr => |ins| self.lowerInsertExpr(ins.expr),
|
||||
.block => self.lowerBlock(node),
|
||||
.jni_env_block => |eb| {
|
||||
// Lexical-direct env resolution (2.16b): evaluate env once,
|
||||
// push onto the env stack, lower body, pop. `#jni_call`
|
||||
// sites inside `eb.body` with an omitted env arg pick up
|
||||
// the top-of-stack value directly — no thread-local read,
|
||||
// env stays register-resident across the body.
|
||||
// Compile-time stack push for lexical-direct env resolution
|
||||
// (2.16b — `#jni_call` in the same fn picks up env from
|
||||
// jni_env_stack directly, no TL read).
|
||||
//
|
||||
// Runtime TL save/set/restore (2.16c) for cross-function
|
||||
// helpers: callees in OTHER fns invoked from inside the
|
||||
// body read the slot via `sx_jni_env_tl_get`. Storage
|
||||
// lives in a separately-linked C helper (see
|
||||
// library/vendors/sx_jni_runtime/sx_jni_env_tl.c) so the
|
||||
// JIT doesn't need orc_rt for TLS.
|
||||
const env_ref = self.lowerExpr(eb.env);
|
||||
const fids = self.getJniEnvTlFids();
|
||||
const ptr_ty = self.module.types.ptrTo(.void);
|
||||
const saved_tl = self.builder.emit(.{ .call = .{ .callee = fids.get, .args = &.{} } }, ptr_ty);
|
||||
const set_args = self.alloc.dupe(Ref, &.{env_ref}) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = fids.set, .args = set_args } }, .void);
|
||||
self.jni_env_stack.append(self.alloc, env_ref) catch unreachable;
|
||||
defer _ = self.jni_env_stack.pop();
|
||||
self.lowerBlock(eb.body);
|
||||
_ = self.jni_env_stack.pop();
|
||||
const restore_args = self.alloc.dupe(Ref, &.{saved_tl}) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = fids.set, .args = restore_args } }, .void);
|
||||
},
|
||||
// Block-local type declarations
|
||||
.struct_decl => |sd| self.registerStructDecl(&sd),
|
||||
@@ -1795,6 +1819,23 @@ pub const Lowering = struct {
|
||||
.spread_expr => self.emitError("spread_expr", node.span),
|
||||
.chained_comparison => |cc| self.lowerChainedComparison(&cc),
|
||||
|
||||
// `#jni_env(env) { body }` in expression position — the block's
|
||||
// value becomes the env-scope's value. Save→set→body-value→restore.
|
||||
.jni_env_block => |eb| blk: {
|
||||
const env_ref = self.lowerExpr(eb.env);
|
||||
const fids = self.getJniEnvTlFids();
|
||||
const ptr_ty = self.module.types.ptrTo(.void);
|
||||
const saved_tl = self.builder.emit(.{ .call = .{ .callee = fids.get, .args = &.{} } }, ptr_ty);
|
||||
const set_args = self.alloc.dupe(Ref, &.{env_ref}) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = fids.set, .args = set_args } }, .void);
|
||||
self.jni_env_stack.append(self.alloc, env_ref) catch unreachable;
|
||||
const value = self.lowerBlockValue(eb.body) orelse self.builder.constInt(0, .void);
|
||||
_ = self.jni_env_stack.pop();
|
||||
const restore_args = self.alloc.dupe(Ref, &.{saved_tl}) catch unreachable;
|
||||
_ = self.builder.emit(.{ .call = .{ .callee = fids.set, .args = restore_args } }, .void);
|
||||
break :blk value;
|
||||
},
|
||||
|
||||
// Statements that can appear in expression position
|
||||
.block => |blk| blk: {
|
||||
// Create a child scope for block-level variable shadowing
|
||||
@@ -3875,35 +3916,31 @@ pub const Lowering = struct {
|
||||
}
|
||||
|
||||
fn lowerJniCall(self: *Lowering, fic: *const ast.FfiIntrinsicCall) Ref {
|
||||
// env disambiguation: the method-name slot is always a string literal,
|
||||
// so its position tells us whether env was omitted.
|
||||
// omitted → args = target, "name", "sig", method-args... (≥3)
|
||||
// explicit → args = env, target, "name", "sig", method-args... (≥4)
|
||||
const env_omitted = fic.args.len >= 3 and fic.args[1].data == .string_literal;
|
||||
const min_arity: usize = if (env_omitted) 3 else 4;
|
||||
if (fic.args.len < min_arity) {
|
||||
// env is always implicit: lexical-direct from the enclosing `#jni_env(env)`
|
||||
// block (2.16b, cheap), else the thread-local slot the block populated
|
||||
// at runtime (2.16c, one TL load per call). Surface form is uniform:
|
||||
// #jni_call(T)(target, "name", "sig", method-args...) (≥3 args)
|
||||
if (fic.args.len < 3) {
|
||||
if (self.diagnostics) |d| {
|
||||
d.add(.err, "#jni_call requires env (optional in #jni_env scope), target, method name, and signature", null);
|
||||
d.add(.err, "#jni_call requires target, method name, and signature", null);
|
||||
}
|
||||
return Ref.none;
|
||||
}
|
||||
|
||||
const ret_ty = self.resolveType(fic.return_type);
|
||||
|
||||
const env_ref = if (env_omitted) blk: {
|
||||
if (self.jni_env_stack.items.len == 0) {
|
||||
if (self.diagnostics) |d| {
|
||||
d.add(.err, "#jni_call with omitted env requires an enclosing #jni_env scope", null);
|
||||
}
|
||||
return Ref.none;
|
||||
}
|
||||
break :blk self.jni_env_stack.items[self.jni_env_stack.items.len - 1];
|
||||
} else self.lowerExpr(fic.args[0]);
|
||||
const env_ref = if (self.jni_env_stack.items.len > self.jni_env_stack_base)
|
||||
self.jni_env_stack.items[self.jni_env_stack.items.len - 1]
|
||||
else blk: {
|
||||
const fids = self.getJniEnvTlFids();
|
||||
const ptr_ty = self.module.types.ptrTo(.void);
|
||||
break :blk self.builder.emit(.{ .call = .{ .callee = fids.get, .args = &.{} } }, ptr_ty);
|
||||
};
|
||||
|
||||
const target_idx: usize = if (env_omitted) 0 else 1;
|
||||
const name_idx: usize = target_idx + 1;
|
||||
const sig_idx: usize = target_idx + 2;
|
||||
const first_method_arg_idx: usize = target_idx + 3;
|
||||
const target_idx: usize = 0;
|
||||
const name_idx: usize = 1;
|
||||
const sig_idx: usize = 2;
|
||||
const first_method_arg_idx: usize = 3;
|
||||
|
||||
const target_ref = self.lowerExpr(fic.args[target_idx]);
|
||||
const name_node = fic.args[name_idx];
|
||||
@@ -8130,6 +8167,37 @@ pub const Lowering = struct {
|
||||
self.foreign_class_map.put(fcd.name, fcd) catch {};
|
||||
}
|
||||
|
||||
/// Lazily declare the `sx_jni_env_tl_get` / `sx_jni_env_tl_set`
|
||||
/// runtime externs (step 2.16c). The storage lives in
|
||||
/// `library/vendors/sx_jni_runtime/sx_jni_env_tl.c` as a
|
||||
/// `_Thread_local` slot — keeping it OUT of the user's IR module
|
||||
/// is what lets the LLVM ORC JIT load the module cleanly without
|
||||
/// orc_rt platform support. AOT targets get the same .c file
|
||||
/// linked in via `needs_jni_env_tl_runtime`, which Compilation
|
||||
/// reads to append a synthetic c_import alongside the user's.
|
||||
fn getJniEnvTlFids(self: *Lowering) struct { get: FuncId, set: FuncId } {
|
||||
self.needs_jni_env_tl_runtime = true;
|
||||
const ptr_ty = self.module.types.ptrTo(.void);
|
||||
if (self.jni_env_tl_get_fid == null) {
|
||||
const name = self.module.types.internString("sx_jni_env_tl_get");
|
||||
const fid = self.builder.declareExtern(name, &.{}, ptr_ty);
|
||||
const func = self.module.getFunctionMut(fid);
|
||||
func.call_conv = .c;
|
||||
self.jni_env_tl_get_fid = fid;
|
||||
}
|
||||
if (self.jni_env_tl_set_fid == null) {
|
||||
const name = self.module.types.internString("sx_jni_env_tl_set");
|
||||
const env_param = self.module.types.internString("env");
|
||||
var params = std.ArrayList(inst_mod.Function.Param).empty;
|
||||
params.append(self.alloc, .{ .name = env_param, .ty = ptr_ty }) catch unreachable;
|
||||
const fid = self.builder.declareExtern(name, params.toOwnedSlice(self.alloc) catch unreachable, .void);
|
||||
const func = self.module.getFunctionMut(fid);
|
||||
func.call_conv = .c;
|
||||
self.jni_env_tl_set_fid = fid;
|
||||
}
|
||||
return .{ .get = self.jni_env_tl_get_fid.?, .set = self.jni_env_tl_set_fid.? };
|
||||
}
|
||||
|
||||
/// When a namespaced import (`Ns :: #import "..."`) contains foreign-class
|
||||
/// declarations, ALSO register them under their qualified name `Ns.Class`
|
||||
/// so receiver types like `*Ns.Class` can find the fcd. The recursive
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1 +1 @@
|
||||
1
|
||||
0
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user