diff --git a/build.zig b/build.zig index d381d17..7746e32 100644 --- a/build.zig +++ b/build.zig @@ -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 = &.{ diff --git a/examples/ffi-jni-call-01-parse.sx b/examples/ffi-jni-call-01-parse.sx index 2180082..f3d6261 100644 --- a/examples/ffi-jni-call-01-parse.sx +++ b/examples/ffi-jni-call-01-parse.sx @@ -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; diff --git a/examples/ffi-jni-call-02-void.sx b/examples/ffi-jni-call-02-void.sx index 73bb5d4..ef4f5e6 100644 --- a/examples/ffi-jni-call-02-void.sx +++ b/examples/ffi-jni-call-02-void.sx @@ -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"); diff --git a/examples/ffi-jni-call-03-methodid-sharing.sx b/examples/ffi-jni-call-03-methodid-sharing.sx index 3379e4a..be3624e 100644 --- a/examples/ffi-jni-call-03-methodid-sharing.sx +++ b/examples/ffi-jni-call-03-methodid-sharing.sx @@ -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 { diff --git a/examples/ffi-jni-call-04-jint-return.sx b/examples/ffi-jni-call-04-jint-return.sx index 3c09fb8..1801df1 100644 --- a/examples/ffi-jni-call-04-jint-return.sx +++ b/examples/ffi-jni-call-04-jint-return.sx @@ -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 { diff --git a/examples/ffi-jni-call-05-jlong-return.sx b/examples/ffi-jni-call-05-jlong-return.sx index d867551..eea619e 100644 --- a/examples/ffi-jni-call-05-jlong-return.sx +++ b/examples/ffi-jni-call-05-jlong-return.sx @@ -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 { diff --git a/examples/ffi-jni-call-06-jdouble-return.sx b/examples/ffi-jni-call-06-jdouble-return.sx index 6a12105..4d1433a 100644 --- a/examples/ffi-jni-call-06-jdouble-return.sx +++ b/examples/ffi-jni-call-06-jdouble-return.sx @@ -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 { diff --git a/examples/ffi-jni-call-07-jboolean-return.sx b/examples/ffi-jni-call-07-jboolean-return.sx index 8cce1f1..2c2f6a8 100644 --- a/examples/ffi-jni-call-07-jboolean-return.sx +++ b/examples/ffi-jni-call-07-jboolean-return.sx @@ -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 { diff --git a/examples/ffi-jni-call-08-jobject-return.sx b/examples/ffi-jni-call-08-jobject-return.sx index 673ff74..b56b818 100644 --- a/examples/ffi-jni-call-08-jobject-return.sx +++ b/examples/ffi-jni-call-08-jobject-return.sx @@ -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 { diff --git a/examples/ffi-jni-call-09-static.sx b/examples/ffi-jni-call-09-static.sx index 7021897..8a3136e 100644 --- a/examples/ffi-jni-call-09-static.sx +++ b/examples/ffi-jni-call-09-static.sx @@ -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 { diff --git a/library/vendors/sx_jni_runtime/sx_jni_env_tl.c b/library/vendors/sx_jni_runtime/sx_jni_env_tl.c new file mode 100644 index 0000000..674bbed --- /dev/null +++ b/library/vendors/sx_jni_runtime/sx_jni_env_tl.c @@ -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 + +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; +} diff --git a/src/core.zig b/src/core.zig index 4373f26..9c58e3f 100644 --- a/src/core.zig +++ b/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; } diff --git a/src/ir/emit_llvm.zig b/src/ir/emit_llvm.zig index ace8faa..e444b99 100644 --- a/src/ir/emit_llvm.zig +++ b/src/ir/emit_llvm.zig @@ -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); diff --git a/src/ir/inst.zig b/src/ir/inst.zig index bfb6fe1..00b5918 100644 --- a/src/ir/inst.zig +++ b/src/ir/inst.zig @@ -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, }; diff --git a/src/ir/lower.zig b/src/ir/lower.zig index f6da606..3970469 100644 --- a/src/ir/lower.zig +++ b/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 diff --git a/tests/expected/ffi-jni-call-03-methodid-sharing.ir b/tests/expected/ffi-jni-call-03-methodid-sharing.ir index 9d815cf..f963b8a 100644 --- a/tests/expected/ffi-jni-call-03-methodid-sharing.ir +++ b/tests/expected/ffi-jni-call-03-methodid-sharing.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-call-04-jint-return.ir b/tests/expected/ffi-jni-call-04-jint-return.ir index da50320..8ac64cc 100644 --- a/tests/expected/ffi-jni-call-04-jint-return.ir +++ b/tests/expected/ffi-jni-call-04-jint-return.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-call-05-jlong-return.ir b/tests/expected/ffi-jni-call-05-jlong-return.ir index 7238eaa..f81d545 100644 --- a/tests/expected/ffi-jni-call-05-jlong-return.ir +++ b/tests/expected/ffi-jni-call-05-jlong-return.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-call-06-jdouble-return.ir b/tests/expected/ffi-jni-call-06-jdouble-return.ir index a66699d..0ddd1e7 100644 --- a/tests/expected/ffi-jni-call-06-jdouble-return.ir +++ b/tests/expected/ffi-jni-call-06-jdouble-return.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-call-07-jboolean-return.ir b/tests/expected/ffi-jni-call-07-jboolean-return.ir index 02772ad..cdaa87b 100644 --- a/tests/expected/ffi-jni-call-07-jboolean-return.ir +++ b/tests/expected/ffi-jni-call-07-jboolean-return.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-call-08-jobject-return.ir b/tests/expected/ffi-jni-call-08-jobject-return.ir index 7c4db6e..d8ff29d 100644 --- a/tests/expected/ffi-jni-call-08-jobject-return.ir +++ b/tests/expected/ffi-jni-call-08-jobject-return.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-call-09-static.ir b/tests/expected/ffi-jni-call-09-static.ir index c58231f..52810b5 100644 --- a/tests/expected/ffi-jni-call-09-static.ir +++ b/tests/expected/ffi-jni-call-09-static.ir @@ -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) - - diff --git a/tests/expected/ffi-jni-class-08-call.ir b/tests/expected/ffi-jni-class-08-call.ir index a9bf4ad..61fd376 100644 --- a/tests/expected/ffi-jni-class-08-call.ir +++ b/tests/expected/ffi-jni-class-08-call.ir @@ -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) diff --git a/tests/expected/ffi-jni-env-02-lexical-direct.ir b/tests/expected/ffi-jni-env-02-lexical-direct.ir index 759bf5a..6ee079d 100644 --- a/tests/expected/ffi-jni-env-02-lexical-direct.ir +++ b/tests/expected/ffi-jni-env-02-lexical-direct.ir @@ -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) diff --git a/tests/expected/ffi-jni-env-03-tl-fallback.exit b/tests/expected/ffi-jni-env-03-tl-fallback.exit index d00491f..573541a 100644 --- a/tests/expected/ffi-jni-env-03-tl-fallback.exit +++ b/tests/expected/ffi-jni-env-03-tl-fallback.exit @@ -1 +1 @@ -1 +0 diff --git a/tests/expected/ffi-jni-env-03-tl-fallback.txt b/tests/expected/ffi-jni-env-03-tl-fallback.txt index e8dc2da..9766475 100644 --- a/tests/expected/ffi-jni-env-03-tl-fallback.txt +++ b/tests/expected/ffi-jni-env-03-tl-fallback.txt @@ -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