refactor(backend): move call + call-extension handlers into ops.zig (A7.4 slice c)
Relocate the Calls (objc_msg_send / jni_msg_send / call / call_indirect) and Call-extensions (call_builtin / compiler_call / call_closure) emitInst handler groups out of emit_llvm.zig into the existing Ops facade. Each emitInst arm now delegates via self.ops().emit<Op>(...). Behavior-preserving pure relocation; emitted LLVM IR is byte-identical (361/0 examples, no snapshot churn). Shared call infra stays on LLVMEmitter, widened pub only as the moved bodies require: extractSlicePtr, loadJniFn, getObjcMsgSendValue, the math F32/F64 declarators + types, getOrDeclareWrite/getWriteType, ffiCtors, materializeByvalArg, emitCStringGlobal, emitJniConstructor, and the Jni slot-offset constants. emitJniConstructor remains in emit_llvm.zig (A7.3 decision); the moved jni arm calls it via self.e.emitJniConstructor(...).
This commit is contained in:
@@ -4,8 +4,10 @@ const c = llvm.c;
|
||||
const emit = @import("../../ir/emit_llvm.zig");
|
||||
const ir_inst = @import("../../ir/inst.zig");
|
||||
const ir_types = @import("../../ir/types.zig");
|
||||
const interp_mod = @import("../../ir/interp.zig");
|
||||
|
||||
const LLVMEmitter = emit.LLVMEmitter;
|
||||
const Interpreter = interp_mod.Interpreter;
|
||||
const Inst = ir_inst.Inst;
|
||||
const BinOp = ir_inst.BinOp;
|
||||
const UnaryOp = ir_inst.UnaryOp;
|
||||
@@ -14,17 +16,24 @@ const Conversion = ir_inst.Conversion;
|
||||
const GlobalId = ir_inst.GlobalId;
|
||||
const GlobalSet = ir_inst.GlobalSet;
|
||||
const FuncId = ir_inst.FuncId;
|
||||
const Call = ir_inst.Call;
|
||||
const CallIndirect = ir_inst.CallIndirect;
|
||||
const ObjcMsgSend = ir_inst.ObjcMsgSend;
|
||||
const JniMsgSend = ir_inst.JniMsgSend;
|
||||
const BuiltinCall = ir_inst.BuiltinCall;
|
||||
const TypeId = ir_types.TypeId;
|
||||
const StringId = ir_types.StringId;
|
||||
|
||||
/// Instruction-emission handlers for `emitInst`: the constant, arithmetic,
|
||||
/// bitwise, comparison, logical, memory, globals, conversion, and pointer
|
||||
/// opcodes. A backend `*LLVMEmitter` facade (field `e`): each method emits one
|
||||
/// opcode's LLVM IR via `self.e.*`. The shared infra these bodies call back into
|
||||
/// (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/`emitCmpOrdered`/
|
||||
/// `emitStrCmp`/`emitStringConstant`/`reflection`/`emitConversion`/`coerceArg`/
|
||||
/// `getRefIRType`) stays on `LLVMEmitter`. `emitInst`'s arms reach these via
|
||||
/// `self.ops()`.
|
||||
/// bitwise, comparison, logical, memory, globals, conversion, pointer, and
|
||||
/// call opcodes (direct/indirect/objc/jni dispatch plus builtin, compiler,
|
||||
/// and closure calls). A backend `*LLVMEmitter` facade (field `e`): each
|
||||
/// method emits one opcode's LLVM IR via `self.e.*`. The shared infra these
|
||||
/// bodies call back into (`mapRef`/`resolveRef`/`matchBinOpTypes`/`emitCmp`/
|
||||
/// `emitCmpOrdered`/`emitStrCmp`/`emitStringConstant`/`reflection`/
|
||||
/// `emitConversion`/`coerceArg`/`getRefIRType`/`loadJniFn`/`extractSlicePtr`/
|
||||
/// `emitJniConstructor`) stays on `LLVMEmitter`. `emitInst`'s arms reach these
|
||||
/// via `self.ops()`.
|
||||
pub const Ops = struct {
|
||||
e: *LLVMEmitter,
|
||||
|
||||
@@ -450,4 +459,689 @@ pub const Ops = struct {
|
||||
const llvm_ty = self.e.toLLVMType(instruction.ty);
|
||||
self.e.mapRef(c.LLVMBuildLoad2(self.e.builder, llvm_ty, ptr, "deref"));
|
||||
}
|
||||
|
||||
// ── Calls ─────────────────────────────────────────────
|
||||
pub fn emitObjcMsgSend(self: Ops, instruction: *const Inst, msg: ObjcMsgSend) void {
|
||||
const msg_send = self.e.getObjcMsgSendValue();
|
||||
// Detect the sret case: >16 B non-HFA struct return.
|
||||
// Same predicate as the plain-foreign-call path so the
|
||||
// two arms stay in lockstep.
|
||||
const raw_ret_ty = self.e.toLLVMType(instruction.ty);
|
||||
const uses_sret = self.e.needsByval(instruction.ty, raw_ret_ty);
|
||||
const ret_ty = if (uses_sret) self.e.cached_void else raw_ret_ty;
|
||||
|
||||
// Slot layout:
|
||||
// uses_sret = false → [recv, sel, args...]
|
||||
// uses_sret = true → [sret_slot, recv, sel, args...]
|
||||
const sret_off: usize = if (uses_sret) 1 else 0;
|
||||
const total_params: usize = 2 + msg.args.len + sret_off;
|
||||
const param_types = self.e.alloc.alloc(c.LLVMTypeRef, total_params) catch unreachable;
|
||||
defer self.e.alloc.free(param_types);
|
||||
const call_args = self.e.alloc.alloc(c.LLVMValueRef, total_params) catch unreachable;
|
||||
defer self.e.alloc.free(call_args);
|
||||
|
||||
var sret_slot: c.LLVMValueRef = null;
|
||||
if (uses_sret) {
|
||||
sret_slot = c.LLVMBuildAlloca(self.e.builder, raw_ret_ty, "objc.sret");
|
||||
param_types[0] = self.e.cached_ptr;
|
||||
call_args[0] = sret_slot;
|
||||
}
|
||||
|
||||
// recv (typed *void from the IR)
|
||||
param_types[sret_off] = self.e.cached_ptr;
|
||||
call_args[sret_off] = self.e.coerceArg(self.e.resolveRef(msg.recv), self.e.cached_ptr);
|
||||
// sel (loaded SEL — opaque ptr)
|
||||
param_types[sret_off + 1] = self.e.cached_ptr;
|
||||
call_args[sret_off + 1] = self.e.coerceArg(self.e.resolveRef(msg.sel), self.e.cached_ptr);
|
||||
// additional args take their IR types, with ABI
|
||||
// coercion applied so structs / strings decay the
|
||||
// same way they do for any C foreign call.
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.e.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.e.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
param_types[i + 2 + sret_off] = coerced_ty;
|
||||
call_args[i + 2 + sret_off] = self.e.coerceArg(self.e.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
|
||||
const fn_ty = c.LLVMFunctionType(ret_ty, param_types.ptr, @intCast(total_params), 0);
|
||||
const call_label: [*:0]const u8 = if (instruction.ty == .void or uses_sret) "" else "objc.msg";
|
||||
var result = c.LLVMBuildCall2(self.e.builder, fn_ty, msg_send, call_args.ptr, @intCast(total_params), call_label);
|
||||
if (uses_sret) {
|
||||
// Tag the call's arg 0 (sret slot) with the sret
|
||||
// attribute so the AArch64 / SysV backends route
|
||||
// through the x8 / hidden-pointer convention.
|
||||
const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4);
|
||||
const sret_attr = c.LLVMCreateTypeAttribute(self.e.context, sret_kind, raw_ret_ty);
|
||||
const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1));
|
||||
c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr);
|
||||
result = c.LLVMBuildLoad2(self.e.builder, raw_ret_ty, sret_slot, "objc.sret.load");
|
||||
}
|
||||
// Always mapRef — the IR Ref counter for this
|
||||
// instruction advances regardless of return type,
|
||||
// so skipping it would misalign every subsequent
|
||||
// ref lookup in this function.
|
||||
self.e.mapRef(result);
|
||||
}
|
||||
|
||||
pub fn emitJniMsgSend(self: Ops, instruction: *const Inst, msg: JniMsgSend) void {
|
||||
// JNI vtable indirection:
|
||||
// ifs = *env // JNINativeInterface*
|
||||
// instance: cls = ifs[GetObjectClass](env, target)
|
||||
// mid = ifs[GetMethodID](env, cls, name, sig)
|
||||
// ifs[Call<T>Method](env, target, mid, args...)
|
||||
// static: target IS the jclass — skip GetObjectClass
|
||||
// mid = ifs[GetStaticMethodID](env, target, name, sig)
|
||||
// ifs[CallStatic<T>Method](env, target, mid, args...)
|
||||
// ctor: cls = ifs[FindClass](env, parent_class_path)
|
||||
// mid = ifs[GetMethodID](env, cls, "<init>", sig)
|
||||
// ifs[NewObject](env, cls, mid, args...) → jobject
|
||||
// nonvirt: handled below via FindClass + GetMethodID +
|
||||
// CallNonvirtual<T>Method.
|
||||
// The cached path (msg.cache_key != null) still shares one
|
||||
// (jclass GlobalRef, jmethodID) pair per literal (name, sig).
|
||||
if (msg.is_constructor) {
|
||||
self.e.emitJniConstructor(msg, instruction.ty);
|
||||
return;
|
||||
}
|
||||
const ret_ty_id = instruction.ty;
|
||||
const is_pointer_ret = switch (self.e.ir_mod.types.get(ret_ty_id)) {
|
||||
.pointer, .many_pointer => true,
|
||||
else => false,
|
||||
};
|
||||
const call_method_offset: u32 = if (msg.is_static) blk: {
|
||||
if (is_pointer_ret) break :blk emit.Jni.CallStaticObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => emit.Jni.CallStaticVoidMethod,
|
||||
.s32 => emit.Jni.CallStaticIntMethod,
|
||||
.s64 => emit.Jni.CallStaticLongMethod,
|
||||
.f32 => emit.Jni.CallStaticFloatMethod,
|
||||
.f64 => emit.Jni.CallStaticDoubleMethod,
|
||||
.bool => emit.Jni.CallStaticBooleanMethod,
|
||||
else => {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
} else if (msg.is_nonvirtual) blk: {
|
||||
if (is_pointer_ret) break :blk emit.Jni.CallNonvirtualObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => emit.Jni.CallNonvirtualVoidMethod,
|
||||
.s32 => emit.Jni.CallNonvirtualIntMethod,
|
||||
.s64 => emit.Jni.CallNonvirtualLongMethod,
|
||||
.f32 => emit.Jni.CallNonvirtualFloatMethod,
|
||||
.f64 => emit.Jni.CallNonvirtualDoubleMethod,
|
||||
.bool => emit.Jni.CallNonvirtualBooleanMethod,
|
||||
else => {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
} else blk: {
|
||||
if (is_pointer_ret) break :blk emit.Jni.CallObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => emit.Jni.CallVoidMethod,
|
||||
.s32 => emit.Jni.CallIntMethod,
|
||||
.s64 => emit.Jni.CallLongMethod,
|
||||
.f32 => emit.Jni.CallFloatMethod,
|
||||
.f64 => emit.Jni.CallDoubleMethod,
|
||||
.bool => emit.Jni.CallBooleanMethod,
|
||||
else => {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
};
|
||||
const get_mid_offset: u32 = if (msg.is_static) emit.Jni.GetStaticMethodID else emit.Jni.GetMethodID;
|
||||
|
||||
const env = self.e.resolveRef(msg.env);
|
||||
const target = self.e.resolveRef(msg.target);
|
||||
// String literals lower as `{ptr, i64}` slices in sx IR;
|
||||
// JNI's `GetMethodID` expects raw C strings, so extract
|
||||
// field 0 when the source is a slice.
|
||||
const name_ptr = self.e.extractSlicePtr(self.e.resolveRef(msg.name));
|
||||
const sig_ptr = self.e.extractSlicePtr(self.e.resolveRef(msg.sig));
|
||||
|
||||
const ifs = c.LLVMBuildLoad2(self.e.builder, self.e.cached_ptr, env, "jni.ifs");
|
||||
|
||||
// Method-ID resolution. When `name` and `sig` are both
|
||||
// string literals the call site participates in
|
||||
// `(name, sig)` slot interning (step 1.17): a shared
|
||||
// pair of static globals holds the `jclass` GlobalRef
|
||||
// and the `jmethodID`, populated lazily on the first
|
||||
// call to any matching site. Non-literal sites fall
|
||||
// back to the per-call `GetObjectClass + GetMethodID`
|
||||
// sequence (1.15 shape).
|
||||
const mid = if (msg.cache_key) |ck| blk: {
|
||||
const pair = self.e.ffiCtors().getOrCreateJniSlots(ck.name_str, ck.sig_str);
|
||||
const cached_mid = c.LLVMBuildLoad2(self.e.builder, self.e.cached_ptr, pair.mid_slot, "jni.cached.mid");
|
||||
const is_cached = c.LLVMBuildICmp(self.e.builder, c.LLVMIntNE, cached_mid, c.LLVMConstNull(self.e.cached_ptr), "jni.is.cached");
|
||||
|
||||
const cur_fn = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.e.builder));
|
||||
const miss_bb = c.LLVMAppendBasicBlockInContext(self.e.context, cur_fn, "jni.miss");
|
||||
const cont_bb = c.LLVMAppendBasicBlockInContext(self.e.context, cur_fn, "jni.cont");
|
||||
const before_bb = c.LLVMGetInsertBlock(self.e.builder);
|
||||
_ = c.LLVMBuildCondBr(self.e.builder, is_cached, cont_bb, miss_bb);
|
||||
|
||||
// Miss path:
|
||||
// instance: GetObjectClass → NewGlobalRef → GetMethodID
|
||||
// static: target IS class → NewGlobalRef(target) → GetStaticMethodID
|
||||
c.LLVMPositionBuilderAtEnd(self.e.builder, miss_bb);
|
||||
const local_cls = if (msg.is_static) target else inst_cls: {
|
||||
const get_obj_cls = self.e.loadJniFn(ifs, emit.Jni.GetObjectClass, "jni.GetObjectClass");
|
||||
var gocls_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr };
|
||||
const gocls_ty = c.LLVMFunctionType(self.e.cached_ptr, &gocls_params, 2, 0);
|
||||
var gocls_args = [_]c.LLVMValueRef{ env, target };
|
||||
break :inst_cls c.LLVMBuildCall2(self.e.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls");
|
||||
};
|
||||
const new_global_ref = self.e.loadJniFn(ifs, emit.Jni.NewGlobalRef, "jni.NewGlobalRef");
|
||||
var ngref_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr };
|
||||
const ngref_ty = c.LLVMFunctionType(self.e.cached_ptr, &ngref_params, 2, 0);
|
||||
var ngref_args = [_]c.LLVMValueRef{ env, local_cls };
|
||||
const global_cls = c.LLVMBuildCall2(self.e.builder, ngref_ty, new_global_ref, &ngref_args, 2, "jni.global.cls");
|
||||
_ = c.LLVMBuildStore(self.e.builder, global_cls, pair.cls_slot);
|
||||
const get_mid = self.e.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID");
|
||||
var gmid_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr };
|
||||
const gmid_ty = c.LLVMFunctionType(self.e.cached_ptr, &gmid_params, 4, 0);
|
||||
var gmid_args = [_]c.LLVMValueRef{ env, global_cls, name_ptr, sig_ptr };
|
||||
const fresh_mid = c.LLVMBuildCall2(self.e.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.fresh.mid");
|
||||
_ = c.LLVMBuildStore(self.e.builder, fresh_mid, pair.mid_slot);
|
||||
const miss_end_bb = c.LLVMGetInsertBlock(self.e.builder);
|
||||
_ = c.LLVMBuildBr(self.e.builder, cont_bb);
|
||||
|
||||
// Cont: phi the cached vs fresh mid.
|
||||
c.LLVMPositionBuilderAtEnd(self.e.builder, cont_bb);
|
||||
const phi = c.LLVMBuildPhi(self.e.builder, self.e.cached_ptr, "jni.mid");
|
||||
var phi_vals = [_]c.LLVMValueRef{ cached_mid, fresh_mid };
|
||||
var phi_blocks = [_]c.LLVMBasicBlockRef{ before_bb, miss_end_bb };
|
||||
c.LLVMAddIncoming(phi, &phi_vals, &phi_blocks, 2);
|
||||
break :blk phi;
|
||||
} else blk: {
|
||||
const cls = if (msg.is_static) target else if (msg.is_nonvirtual) nonvirt_cls: {
|
||||
// `super.method(args)`: dispatch is bound to a
|
||||
// specific class (the parent), not subclass-override.
|
||||
// Resolve via FindClass(parent_path). No caching yet —
|
||||
// per-call lookup. The parent path is a NUL-terminated
|
||||
// C string emitted as a private LLVM global.
|
||||
const path = msg.parent_class_path orelse "";
|
||||
const path_global = self.e.emitCStringGlobal(path, "jni.parent.path");
|
||||
const find_class = self.e.loadJniFn(ifs, emit.Jni.FindClass, "jni.FindClass");
|
||||
var fc_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr };
|
||||
const fc_ty = c.LLVMFunctionType(self.e.cached_ptr, &fc_params, 2, 0);
|
||||
var fc_args = [_]c.LLVMValueRef{ env, path_global };
|
||||
break :nonvirt_cls c.LLVMBuildCall2(self.e.builder, fc_ty, find_class, &fc_args, 2, "jni.parent.cls");
|
||||
} else inst_cls: {
|
||||
const get_obj_cls = self.e.loadJniFn(ifs, emit.Jni.GetObjectClass, "jni.GetObjectClass");
|
||||
var gocls_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr };
|
||||
const gocls_ty = c.LLVMFunctionType(self.e.cached_ptr, &gocls_params, 2, 0);
|
||||
var gocls_args = [_]c.LLVMValueRef{ env, target };
|
||||
break :inst_cls c.LLVMBuildCall2(self.e.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls");
|
||||
};
|
||||
const get_mid = self.e.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID");
|
||||
var gmid_params = [_]c.LLVMTypeRef{ self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr, self.e.cached_ptr };
|
||||
const gmid_ty = c.LLVMFunctionType(self.e.cached_ptr, &gmid_params, 4, 0);
|
||||
var gmid_args = [_]c.LLVMValueRef{ env, cls, name_ptr, sig_ptr };
|
||||
const mid_val = c.LLVMBuildCall2(self.e.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.mid");
|
||||
if (msg.is_nonvirtual) {
|
||||
// Stash cls in a dummy slot so the call site below
|
||||
// can pick it up. Easiest path: do the call right
|
||||
// here and return Ref.none, but we need to keep the
|
||||
// outer phi shape. Instead, return both via tuple
|
||||
// through an auxiliary local — simplest is to attach
|
||||
// `cls` to a per-invocation slot. Use a stack alloca.
|
||||
const cls_slot = c.LLVMBuildAlloca(self.e.builder, self.e.cached_ptr, "jni.parent.cls.slot");
|
||||
_ = c.LLVMBuildStore(self.e.builder, cls, cls_slot);
|
||||
// Tag the slot pointer onto the phi result via the
|
||||
// generated metadata: we'll re-extract by re-running
|
||||
// FindClass — actually simpler: lower nonvirtual on
|
||||
// the spot below. Drop the implicit `break` here:
|
||||
const call_fn = self.e.loadJniFn(ifs, call_method_offset, "jni.callfn.nonvirtual");
|
||||
const raw_ret = self.e.toLLVMType(ret_ty_id);
|
||||
const total_call_params_nv: usize = 4 + msg.args.len;
|
||||
const call_param_types_nv = self.e.alloc.alloc(c.LLVMTypeRef, total_call_params_nv) catch unreachable;
|
||||
defer self.e.alloc.free(call_param_types_nv);
|
||||
const call_args_nv = self.e.alloc.alloc(c.LLVMValueRef, total_call_params_nv) catch unreachable;
|
||||
defer self.e.alloc.free(call_args_nv);
|
||||
call_param_types_nv[0] = self.e.cached_ptr;
|
||||
call_param_types_nv[1] = self.e.cached_ptr;
|
||||
call_param_types_nv[2] = self.e.cached_ptr;
|
||||
call_param_types_nv[3] = self.e.cached_ptr;
|
||||
call_args_nv[0] = env;
|
||||
call_args_nv[1] = target;
|
||||
call_args_nv[2] = cls;
|
||||
call_args_nv[3] = mid_val;
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.e.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.e.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
call_param_types_nv[i + 4] = coerced_ty;
|
||||
call_args_nv[i + 4] = self.e.coerceArg(self.e.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
const call_fn_ty_nv = c.LLVMFunctionType(raw_ret, call_param_types_nv.ptr, @intCast(total_call_params_nv), 0);
|
||||
const label_nv: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.nonvirtual.ret";
|
||||
const result_nv = c.LLVMBuildCall2(self.e.builder, call_fn_ty_nv, call_fn, call_args_nv.ptr, @intCast(total_call_params_nv), label_nv);
|
||||
self.e.mapRef(result_nv);
|
||||
return;
|
||||
}
|
||||
break :blk mid_val;
|
||||
};
|
||||
|
||||
// Call<Type>Method: (JNIEnv*, jobject, jmethodID, args...) -> RetTy
|
||||
const call_fn = self.e.loadJniFn(ifs, call_method_offset, "jni.callfn");
|
||||
const raw_ret = self.e.toLLVMType(ret_ty_id);
|
||||
const total_call_params: usize = 3 + msg.args.len;
|
||||
const call_param_types = self.e.alloc.alloc(c.LLVMTypeRef, total_call_params) catch unreachable;
|
||||
defer self.e.alloc.free(call_param_types);
|
||||
const call_args = self.e.alloc.alloc(c.LLVMValueRef, total_call_params) catch unreachable;
|
||||
defer self.e.alloc.free(call_args);
|
||||
call_param_types[0] = self.e.cached_ptr;
|
||||
call_param_types[1] = self.e.cached_ptr;
|
||||
call_param_types[2] = self.e.cached_ptr;
|
||||
call_args[0] = env;
|
||||
call_args[1] = target;
|
||||
call_args[2] = mid;
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.e.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.e.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.e.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
call_param_types[i + 3] = coerced_ty;
|
||||
call_args[i + 3] = self.e.coerceArg(self.e.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
const call_fn_ty = c.LLVMFunctionType(raw_ret, call_param_types.ptr, @intCast(total_call_params), 0);
|
||||
const label: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.ret";
|
||||
const result = c.LLVMBuildCall2(self.e.builder, call_fn_ty, call_fn, call_args.ptr, @intCast(total_call_params), label);
|
||||
self.e.mapRef(result);
|
||||
}
|
||||
|
||||
pub fn emitCall(self: Ops, instruction: *const Inst, call_op: Call) void {
|
||||
// Evaluate comptime functions at compile time
|
||||
const callee_func = &self.e.ir_mod.functions.items[call_op.callee.index()];
|
||||
if (callee_func.is_comptime and call_op.args.len == 0) {
|
||||
var interp_inst = Interpreter.init(self.e.ir_mod, self.e.alloc);
|
||||
interp_inst.build_config = &self.e.build_config;
|
||||
if (self.e.import_sources) |sm| interp_inst.setSourceMap(sm);
|
||||
defer interp_inst.deinit();
|
||||
if (interp_inst.call(call_op.callee, &.{})) |result| {
|
||||
if (result.asInt()) |v| {
|
||||
self.e.mapRef(c.LLVMConstInt(self.e.toLLVMType(instruction.ty), @bitCast(v), 0));
|
||||
return;
|
||||
} else if (result.asFloat()) |v| {
|
||||
self.e.mapRef(c.LLVMConstReal(self.e.toLLVMType(instruction.ty), v));
|
||||
return;
|
||||
} else if (result.asBool()) |v| {
|
||||
self.e.mapRef(c.LLVMConstInt(self.e.toLLVMType(instruction.ty), @intFromBool(v), 0));
|
||||
return;
|
||||
} else if (result == .string) {
|
||||
self.e.mapRef(self.e.emitStringConstant(result.string));
|
||||
return;
|
||||
}
|
||||
} else |_| {}
|
||||
}
|
||||
const callee = self.e.func_map.get(call_op.callee.index()) orelse {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
};
|
||||
const callee_needs_c_abi = callee_func.is_extern or callee_func.call_conv == .c;
|
||||
const callee_raw_ret = self.e.toLLVMType(callee_func.ret);
|
||||
const callee_uses_sret = callee_needs_c_abi and self.e.needsByval(callee_func.ret, callee_raw_ret);
|
||||
|
||||
// When the callee uses sret, prepend an alloca for the result.
|
||||
// Index alignment: actual_args[0] = sret_slot; actual_args[i+1] = sx arg i.
|
||||
const sret_off: usize = if (callee_uses_sret) 1 else 0;
|
||||
const total_args = call_op.args.len + sret_off;
|
||||
const args = self.e.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable;
|
||||
defer self.e.alloc.free(args);
|
||||
var sret_slot: c.LLVMValueRef = null;
|
||||
if (callee_uses_sret) {
|
||||
sret_slot = c.LLVMBuildAlloca(self.e.builder, callee_raw_ret, "sret.slot");
|
||||
args[0] = sret_slot;
|
||||
}
|
||||
for (call_op.args, 0..) |arg_ref, j| {
|
||||
args[j + sret_off] = self.e.resolveRef(arg_ref);
|
||||
}
|
||||
const arg_count: c_uint = @intCast(total_args);
|
||||
|
||||
// Get the function type from LLVM and coerce arguments
|
||||
const fn_ty = c.LLVMGlobalGetValueType(callee);
|
||||
const param_count = c.LLVMCountParamTypes(fn_ty);
|
||||
if (param_count > 0) {
|
||||
const param_types = self.e.alloc.alloc(c.LLVMTypeRef, param_count) catch unreachable;
|
||||
defer self.e.alloc.free(param_types);
|
||||
c.LLVMGetParamTypes(fn_ty, param_types.ptr);
|
||||
for (0..@min(args.len, param_count)) |j| {
|
||||
// The sret slot is already a properly-typed pointer; skip coercion.
|
||||
if (callee_uses_sret and j == 0) continue;
|
||||
const fn_param_idx = j - sret_off;
|
||||
// Materialize byval args before coercion so we pass a ptr instead of the struct value.
|
||||
if (callee_needs_c_abi and fn_param_idx < callee_func.params.len) {
|
||||
const ir_ty = callee_func.params[fn_param_idx].ty;
|
||||
const raw_struct = self.e.toLLVMType(ir_ty);
|
||||
if (self.e.needsByval(ir_ty, raw_struct)) {
|
||||
args[j] = self.e.materializeByvalArg(args[j], raw_struct);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
args[j] = self.e.coerceArg(args[j], param_types[j]);
|
||||
}
|
||||
}
|
||||
// A `void`/`noreturn` call has no value, so it must stay
|
||||
// unnamed (LLVM rejects a named void result).
|
||||
const call_is_void_like = instruction.ty == .void or instruction.ty == .noreturn;
|
||||
const call_label: [*:0]const u8 = if (call_is_void_like or callee_uses_sret) "" else "call";
|
||||
var result = c.LLVMBuildCall2(self.e.builder, fn_ty, callee, args.ptr, arg_count, call_label);
|
||||
if (callee_uses_sret) {
|
||||
// Mirror the function-decl `sret(<T>)` attribute on the call site so the
|
||||
// LLVM backend lowers arg 0 via x8 (AAPCS64) / hidden ptr (SysV AMD64).
|
||||
const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4);
|
||||
const sret_attr = c.LLVMCreateTypeAttribute(self.e.context, sret_kind, callee_raw_ret);
|
||||
const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1));
|
||||
c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr);
|
||||
// Load the actual struct value the callee wrote into the slot.
|
||||
result = c.LLVMBuildLoad2(self.e.builder, callee_raw_ret, sret_slot, "sret.load");
|
||||
} else if (!call_is_void_like and callee_func.is_extern) {
|
||||
// Coerce ABI return value (e.g. i64 / [2 x i64]) back to IR struct type if needed
|
||||
const expected_ty = self.e.toLLVMType(instruction.ty);
|
||||
result = self.e.coerceArg(result, expected_ty);
|
||||
}
|
||||
self.e.mapRef(result);
|
||||
}
|
||||
|
||||
pub fn emitCallIndirect(self: Ops, instruction: *const Inst, call_op: CallIndirect) void {
|
||||
const callee = self.e.resolveRef(call_op.callee);
|
||||
const arg_count: c_uint = @intCast(call_op.args.len);
|
||||
const args = self.e.alloc.alloc(c.LLVMValueRef, call_op.args.len) catch unreachable;
|
||||
defer self.e.alloc.free(args);
|
||||
for (call_op.args, 0..) |arg_ref, j| {
|
||||
args[j] = self.e.resolveRef(arg_ref);
|
||||
}
|
||||
|
||||
// Get callee's IR type to resolve parameter types accurately
|
||||
const callee_ir_ty = self.e.getRefIRType(call_op.callee);
|
||||
const fn_params: ?[]const TypeId = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.e.ir_mod.types.get(cty);
|
||||
switch (ci) {
|
||||
.function => |f| break :blk f.params,
|
||||
.closure => |cl| break :blk cl.params,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
break :blk null;
|
||||
} else null;
|
||||
|
||||
// Read the fn-pointer type's calling convention. Only `.c` opts
|
||||
// into the C-ABI byval coercion for >16B aggregate params.
|
||||
const fp_is_c_abi: bool = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.e.ir_mod.types.get(cty);
|
||||
if (ci == .function and ci.function.call_conv == .c) break :blk true;
|
||||
}
|
||||
break :blk false;
|
||||
} else false;
|
||||
|
||||
// Default-conv fn-pointers under implicit-ctx carry a hidden
|
||||
// `*void` (the implicit __sx_ctx) at LLVM slot 0. The IR fn
|
||||
// type does not include it, so shift fn_params lookups by 1.
|
||||
const fp_ctx_slots: usize = if (callee_ir_ty) |cty| blk: {
|
||||
if (!self.e.ir_mod.has_implicit_ctx) break :blk 0;
|
||||
if (cty.isBuiltin()) break :blk 0;
|
||||
const ci = self.e.ir_mod.types.get(cty);
|
||||
switch (ci) {
|
||||
.function => |f| break :blk if (f.call_conv == .c) @as(usize, 0) else 1,
|
||||
else => break :blk 0,
|
||||
}
|
||||
} else 0;
|
||||
|
||||
const ret_ty = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.e.ir_mod.types.get(cty);
|
||||
switch (ci) {
|
||||
.function => |f| break :blk self.e.toLLVMType(f.ret),
|
||||
.closure => |cl| break :blk self.e.toLLVMType(cl.ret),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
break :blk self.e.toLLVMType(instruction.ty);
|
||||
} else self.e.toLLVMType(instruction.ty);
|
||||
|
||||
const param_tys = self.e.alloc.alloc(c.LLVMTypeRef, call_op.args.len) catch unreachable;
|
||||
defer self.e.alloc.free(param_tys);
|
||||
if (fn_params) |fp| {
|
||||
for (0..call_op.args.len) |j| {
|
||||
// Slots 0..fp_ctx_slots are the implicit __sx_ctx
|
||||
// (passed as opaque ptr; not in fp).
|
||||
if (j < fp_ctx_slots) {
|
||||
param_tys[j] = self.e.cached_ptr;
|
||||
args[j] = self.e.coerceArg(args[j], self.e.cached_ptr);
|
||||
continue;
|
||||
}
|
||||
const fp_idx = j - fp_ctx_slots;
|
||||
if (fp_idx < fp.len) {
|
||||
const raw_struct = self.e.toLLVMType(fp[fp_idx]);
|
||||
if (fp_is_c_abi and self.e.needsByval(fp[fp_idx], raw_struct)) {
|
||||
args[j] = self.e.materializeByvalArg(args[j], raw_struct);
|
||||
param_tys[j] = self.e.cached_ptr;
|
||||
continue;
|
||||
}
|
||||
var llvm_pty = raw_struct;
|
||||
// Array params in fn-ptr calls decay to pointers (C ABI)
|
||||
if (c.LLVMGetTypeKind(llvm_pty) == c.LLVMArrayTypeKind) {
|
||||
llvm_pty = self.e.cached_ptr;
|
||||
}
|
||||
param_tys[j] = llvm_pty;
|
||||
args[j] = self.e.coerceArg(args[j], llvm_pty);
|
||||
} else {
|
||||
param_tys[j] = c.LLVMTypeOf(args[j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (args, 0..) |arg, j| {
|
||||
param_tys[j] = c.LLVMTypeOf(arg);
|
||||
}
|
||||
}
|
||||
const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, arg_count, 0);
|
||||
const icall_void_like = instruction.ty == .void or instruction.ty == .noreturn;
|
||||
var result = c.LLVMBuildCall2(self.e.builder, fn_ty, callee, args.ptr, arg_count, if (icall_void_like) "" else "icall");
|
||||
|
||||
// Coerce call result to instruction's expected type
|
||||
const expected_ty = self.e.toLLVMType(instruction.ty);
|
||||
if (!icall_void_like and c.LLVMTypeOf(result) != expected_ty) {
|
||||
result = self.e.coerceArg(result, expected_ty);
|
||||
}
|
||||
self.e.mapRef(result);
|
||||
}
|
||||
|
||||
// ── Call extensions ───────────────────────────────────────
|
||||
pub fn emitCallBuiltin(self: Ops, instruction: *const Inst, bi: BuiltinCall) void {
|
||||
// Builtins that map to libc functions or LLVM intrinsics
|
||||
switch (bi.builtin) {
|
||||
.sqrt, .sin, .cos, .floor => {
|
||||
const val = self.e.resolveRef(bi.args[0]);
|
||||
const val_ty = c.LLVMTypeOf(val);
|
||||
const val_kind = c.LLVMGetTypeKind(val_ty);
|
||||
if (val_kind == c.LLVMFloatTypeKind) {
|
||||
const f = self.e.getOrDeclareMathF32(bi.builtin);
|
||||
var args = [_]c.LLVMValueRef{val};
|
||||
self.e.mapRef(c.LLVMBuildCall2(self.e.builder, self.e.getMathF32Type(), f, &args, 1, @tagName(bi.builtin)));
|
||||
} else {
|
||||
const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.e.coerceArg(val, self.e.cached_f64) else val;
|
||||
const f = self.e.getOrDeclareMathF64(bi.builtin);
|
||||
var args = [_]c.LLVMValueRef{coerced};
|
||||
self.e.mapRef(c.LLVMBuildCall2(self.e.builder, self.e.getMathF64Type(), f, &args, 1, @tagName(bi.builtin)));
|
||||
}
|
||||
},
|
||||
.out => {
|
||||
// out(str): extract ptr and len from string fat pointer, call write(1, ptr, len)
|
||||
const str_val = self.e.resolveRef(bi.args[0]);
|
||||
const raw_ptr = c.LLVMBuildExtractValue(self.e.builder, str_val, 0, "str.ptr");
|
||||
const str_len = c.LLVMBuildExtractValue(self.e.builder, str_val, 1, "str.len");
|
||||
// On wasm32, count param is i32 (size_t)
|
||||
const count = if (self.e.target_config.isWasm32())
|
||||
c.LLVMBuildTrunc(self.e.builder, str_len, self.e.cached_i32, "len.tr")
|
||||
else
|
||||
str_len;
|
||||
const write_fn = self.e.getOrDeclareWrite();
|
||||
var write_args = [_]c.LLVMValueRef{
|
||||
c.LLVMConstInt(self.e.cached_i32, 1, 0), // fd = stdout
|
||||
raw_ptr,
|
||||
count,
|
||||
};
|
||||
_ = c.LLVMBuildCall2(self.e.builder, self.e.getWriteType(), write_fn, &write_args, 3, "");
|
||||
self.e.advanceRefCounter();
|
||||
},
|
||||
.type_name => {
|
||||
// Dynamic `type_name(t)` at runtime: extract
|
||||
// the TypeId from the arg (an Any-boxed Type
|
||||
// value: tag=`.s64.index()`, value=tid), GEP
|
||||
// into the compiler-emitted `__sx_type_names`
|
||||
// global, load the string. The arg's LLVM
|
||||
// shape is the `{i64, i64}` Any aggregate
|
||||
// (because the IR-side arg type is `.any`
|
||||
// when boxed); for unboxed direct call sites
|
||||
// (the arg IR type is `.s64` from
|
||||
// `const_type`), the value IS the TypeId
|
||||
// index directly.
|
||||
const arg_ref = bi.args[0];
|
||||
const arg_val = self.e.resolveRef(arg_ref);
|
||||
const arg_ir_ty = self.e.getRefIRType(arg_ref) orelse TypeId.s64;
|
||||
const tid_idx = blk: {
|
||||
if (arg_ir_ty == .any) {
|
||||
// Boxed: extract value field.
|
||||
break :blk c.LLVMBuildExtractValue(self.e.builder, arg_val, 1, "tn.tid");
|
||||
}
|
||||
// Bare i64 (TypeId index).
|
||||
break :blk arg_val;
|
||||
};
|
||||
const arr_global = self.e.reflection().getOrBuildTypeNameArray();
|
||||
const arr_len = self.e.type_name_array_len;
|
||||
const string_ty = self.e.getStringStructType();
|
||||
const arr_ty = c.LLVMArrayType(string_ty, arr_len);
|
||||
const zero = c.LLVMConstInt(self.e.cached_i64, 0, 0);
|
||||
var indices = [2]c.LLVMValueRef{ zero, tid_idx };
|
||||
const gep = c.LLVMBuildInBoundsGEP2(self.e.builder, arr_ty, arr_global, &indices, 2, "tn.gep");
|
||||
const result = c.LLVMBuildLoad2(self.e.builder, string_ty, gep, "tn.load");
|
||||
self.e.mapRef(result);
|
||||
},
|
||||
.type_eq => {
|
||||
// Dynamic `type_eq(a, b)` — both args are
|
||||
// Type values. Extract TypeId from each Any
|
||||
// box (or use directly if `.s64`-typed),
|
||||
// icmp eq.
|
||||
const a = blk: {
|
||||
const v = self.e.resolveRef(bi.args[0]);
|
||||
const ty = self.e.getRefIRType(bi.args[0]) orelse TypeId.s64;
|
||||
if (ty == .any) break :blk c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.a");
|
||||
break :blk v;
|
||||
};
|
||||
const b = blk: {
|
||||
const v = self.e.resolveRef(bi.args[1]);
|
||||
const ty = self.e.getRefIRType(bi.args[1]) orelse TypeId.s64;
|
||||
if (ty == .any) break :blk c.LLVMBuildExtractValue(self.e.builder, v, 1, "te.b");
|
||||
break :blk v;
|
||||
};
|
||||
const eq_res = c.LLVMBuildICmp(self.e.builder, c.LLVMIntEQ, a, b, "te.eq");
|
||||
self.e.mapRef(eq_res);
|
||||
},
|
||||
.has_impl => {
|
||||
// Runtime has_impl needs a protocol-map
|
||||
// snapshot — not wired yet. Silent false for
|
||||
// now; the lower-time fold via
|
||||
// `tryConstBoolCondition` covers every
|
||||
// statically-resolvable call.
|
||||
self.e.mapRef(c.LLVMConstInt(self.e.cached_i1, 0, 0));
|
||||
},
|
||||
else => {
|
||||
// size_of, cast — handled by lowering or codegen glue
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emitCompilerCall(self: Ops, instruction: *const Inst) void {
|
||||
// Compiler hooks are comptime-only; if one reaches emission, produce undef
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
}
|
||||
|
||||
pub fn emitCallClosure(self: Ops, instruction: *const Inst, call_op: CallIndirect) void {
|
||||
// Closure: { fn_ptr, env }.
|
||||
//
|
||||
// ABI (when module.has_implicit_ctx):
|
||||
// trampoline signature: (__sx_ctx, env, args...)
|
||||
// call_op.args[0] = __sx_ctx (prepended by lowering)
|
||||
// call_op.args[1..] = user args
|
||||
// extracted env_ptr = inserted at LLVM slot 1
|
||||
//
|
||||
// ABI (without implicit_ctx):
|
||||
// trampoline signature: (env, args...)
|
||||
// call_op.args = user args (no ctx prepend)
|
||||
// extracted env_ptr = inserted at LLVM slot 0
|
||||
const closure = self.e.resolveRef(call_op.callee);
|
||||
const cl_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(closure));
|
||||
if (cl_kind != c.LLVMStructTypeKind) {
|
||||
self.e.mapRef(c.LLVMGetUndef(self.e.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
}
|
||||
const fn_ptr = c.LLVMBuildExtractValue(self.e.builder, closure, 0, "cl.fn");
|
||||
const env_ptr = c.LLVMBuildExtractValue(self.e.builder, closure, 1, "cl.env");
|
||||
|
||||
// Get the closure's declared parameter types from the IR type system
|
||||
const callee_ir_ty = self.e.getRefIRType(call_op.callee);
|
||||
const closure_params: ?[]const TypeId = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.e.ir_mod.types.get(cty);
|
||||
if (ci == .closure) break :blk ci.closure.params;
|
||||
}
|
||||
break :blk null;
|
||||
} else null;
|
||||
|
||||
const has_ctx = self.e.ir_mod.has_implicit_ctx;
|
||||
const user_args_offset_in_op: usize = if (has_ctx) 1 else 0;
|
||||
const user_args_count: usize = call_op.args.len -| user_args_offset_in_op;
|
||||
const ctx_slots: usize = if (has_ctx) 1 else 0;
|
||||
const total_args = ctx_slots + 1 + user_args_count; // [ctx?] + env + user_args
|
||||
|
||||
const args = self.e.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable;
|
||||
defer self.e.alloc.free(args);
|
||||
if (has_ctx) {
|
||||
args[0] = self.e.resolveRef(call_op.args[0]); // ctx
|
||||
}
|
||||
args[ctx_slots] = env_ptr;
|
||||
for (0..user_args_count) |j| {
|
||||
args[ctx_slots + 1 + j] = self.e.resolveRef(call_op.args[user_args_offset_in_op + j]);
|
||||
}
|
||||
|
||||
// Build function type using declared param types (not arg types).
|
||||
// closure_params is user-visible (no ctx, no env), so they line
|
||||
// up with args[ctx_slots+1..].
|
||||
const ret_ty = self.e.toLLVMType(instruction.ty);
|
||||
const param_tys = self.e.alloc.alloc(c.LLVMTypeRef, total_args) catch unreachable;
|
||||
defer self.e.alloc.free(param_tys);
|
||||
if (has_ctx) param_tys[0] = self.e.cached_ptr; // __sx_ctx
|
||||
param_tys[ctx_slots] = self.e.cached_ptr; // env
|
||||
if (closure_params) |cp| {
|
||||
for (0..user_args_count) |j| {
|
||||
const param_ir_ty = if (j < cp.len) cp[j] else null;
|
||||
if (param_ir_ty) |pty| {
|
||||
const llvm_pty = self.e.toLLVMType(pty);
|
||||
param_tys[ctx_slots + 1 + j] = llvm_pty;
|
||||
args[ctx_slots + 1 + j] = self.e.coerceArg(args[ctx_slots + 1 + j], llvm_pty);
|
||||
} else {
|
||||
param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (0..user_args_count) |j| {
|
||||
param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]);
|
||||
}
|
||||
}
|
||||
const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, @intCast(total_args), 0);
|
||||
|
||||
const is_void = instruction.ty == .void;
|
||||
const result = c.LLVMBuildCall2(self.e.builder, fn_ty, fn_ptr, args.ptr, @intCast(total_args), if (is_void) "" else "ccall");
|
||||
if (!is_void) {
|
||||
self.e.mapRef(result);
|
||||
} else {
|
||||
self.e.advanceRefCounter();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -50,46 +50,46 @@ fn isIdentByte(b: u8) bool {
|
||||
/// spec across versions; locked to the documented order in
|
||||
/// `<jni.h>`. Slot numbers here MUST match the order of fields in
|
||||
/// the C `JNINativeInterface_` struct.
|
||||
const Jni = struct {
|
||||
const FindClass: u32 = 6;
|
||||
const NewGlobalRef: u32 = 21;
|
||||
const NewObject: u32 = 28;
|
||||
const GetObjectClass: u32 = 31;
|
||||
const GetMethodID: u32 = 33;
|
||||
pub const Jni = struct {
|
||||
pub const FindClass: u32 = 6;
|
||||
pub const NewGlobalRef: u32 = 21;
|
||||
pub const NewObject: u32 = 28;
|
||||
pub const GetObjectClass: u32 = 31;
|
||||
pub const GetMethodID: u32 = 33;
|
||||
// Call<Type>Method (instance, varargs variant). Each numeric type
|
||||
// has its own slot — distinct ABI per return type, so the JNI
|
||||
// runtime dispatches the right arg-shuffle for each.
|
||||
const CallObjectMethod: u32 = 34;
|
||||
const CallBooleanMethod: u32 = 37;
|
||||
const CallIntMethod: u32 = 49;
|
||||
const CallLongMethod: u32 = 52;
|
||||
const CallFloatMethod: u32 = 55;
|
||||
const CallDoubleMethod: u32 = 58;
|
||||
const CallVoidMethod: u32 = 61;
|
||||
pub const CallObjectMethod: u32 = 34;
|
||||
pub const CallBooleanMethod: u32 = 37;
|
||||
pub const CallIntMethod: u32 = 49;
|
||||
pub const CallLongMethod: u32 = 52;
|
||||
pub const CallFloatMethod: u32 = 55;
|
||||
pub const CallDoubleMethod: u32 = 58;
|
||||
pub const CallVoidMethod: u32 = 61;
|
||||
// CallNonvirtual<T>Method (instance, super-dispatch variant). Used by
|
||||
// `super.method(args)` from inside a `#jni_main` Activity method body:
|
||||
// dispatch is bound to a specific class rather than going through the
|
||||
// vtable, so subclass overrides don't intercept the call. Signature:
|
||||
// `(JNIEnv*, jobject obj, jclass clazz, jmethodID, args...)`.
|
||||
const CallNonvirtualObjectMethod: u32 = 64;
|
||||
const CallNonvirtualBooleanMethod: u32 = 67;
|
||||
const CallNonvirtualIntMethod: u32 = 79;
|
||||
const CallNonvirtualLongMethod: u32 = 82;
|
||||
const CallNonvirtualFloatMethod: u32 = 85;
|
||||
const CallNonvirtualDoubleMethod: u32 = 88;
|
||||
const CallNonvirtualVoidMethod: u32 = 91;
|
||||
pub const CallNonvirtualObjectMethod: u32 = 64;
|
||||
pub const CallNonvirtualBooleanMethod: u32 = 67;
|
||||
pub const CallNonvirtualIntMethod: u32 = 79;
|
||||
pub const CallNonvirtualLongMethod: u32 = 82;
|
||||
pub const CallNonvirtualFloatMethod: u32 = 85;
|
||||
pub const CallNonvirtualDoubleMethod: u32 = 88;
|
||||
pub const CallNonvirtualVoidMethod: u32 = 91;
|
||||
// Static-dispatch siblings — `target` IS already a `jclass`, so
|
||||
// no `GetObjectClass` step. `GetStaticMethodID` returns a
|
||||
// method-ID that's bound to a class+method+sig like the instance
|
||||
// variant; `CallStatic<Type>Method` dispatches without a `this`.
|
||||
const GetStaticMethodID: u32 = 113;
|
||||
const CallStaticObjectMethod: u32 = 114;
|
||||
const CallStaticBooleanMethod: u32 = 117;
|
||||
const CallStaticIntMethod: u32 = 129;
|
||||
const CallStaticLongMethod: u32 = 132;
|
||||
const CallStaticFloatMethod: u32 = 135;
|
||||
const CallStaticDoubleMethod: u32 = 138;
|
||||
const CallStaticVoidMethod: u32 = 141;
|
||||
pub const GetStaticMethodID: u32 = 113;
|
||||
pub const CallStaticObjectMethod: u32 = 114;
|
||||
pub const CallStaticBooleanMethod: u32 = 117;
|
||||
pub const CallStaticIntMethod: u32 = 129;
|
||||
pub const CallStaticLongMethod: u32 = 132;
|
||||
pub const CallStaticFloatMethod: u32 = 135;
|
||||
pub const CallStaticDoubleMethod: u32 = 138;
|
||||
pub const CallStaticVoidMethod: u32 = 141;
|
||||
};
|
||||
|
||||
// ── LLVMEmitter ─────────────────────────────────────────────────────────
|
||||
@@ -641,7 +641,7 @@ pub const LLVMEmitter = struct {
|
||||
/// (the ptr); otherwise return it unchanged. Used by JNI dispatch
|
||||
/// to feed string-literal method names + signatures to
|
||||
/// `GetMethodID`, which expects raw C strings.
|
||||
fn extractSlicePtr(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
|
||||
pub fn extractSlicePtr(self: *LLVMEmitter, val: c.LLVMValueRef) c.LLVMValueRef {
|
||||
const val_ty = c.LLVMTypeOf(val);
|
||||
if (c.LLVMGetTypeKind(val_ty) != c.LLVMStructTypeKind) return val;
|
||||
if (c.LLVMCountStructElementTypes(val_ty) != 2) return val;
|
||||
@@ -653,7 +653,7 @@ pub const LLVMEmitter = struct {
|
||||
/// Load a JNI vtable function pointer at the given offset. `ifs`
|
||||
/// is the `JNINativeInterface*` loaded from `JNIEnv*`. Treats the
|
||||
/// vtable as an array of opaque `ptr`s and indexes into it.
|
||||
fn loadJniFn(self: *LLVMEmitter, ifs: c.LLVMValueRef, offset: u32, name: [*:0]const u8) c.LLVMValueRef {
|
||||
pub fn loadJniFn(self: *LLVMEmitter, ifs: c.LLVMValueRef, offset: u32, name: [*:0]const u8) c.LLVMValueRef {
|
||||
const offset_val = c.LLVMConstInt(self.cached_i32, offset, 0);
|
||||
var idx = [_]c.LLVMValueRef{offset_val};
|
||||
const slot = c.LLVMBuildInBoundsGEP2(self.builder, self.cached_ptr, ifs, &idx, 1, "");
|
||||
@@ -664,7 +664,7 @@ pub const LLVMEmitter = struct {
|
||||
/// Cached on the emitter; all `objc_msg_send` instructions hand
|
||||
/// LLVMBuildCall2 their own per-call-site function type — the
|
||||
/// underlying function value is just an opaque `ptr` symbol.
|
||||
fn getObjcMsgSendValue(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
pub fn getObjcMsgSendValue(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
if (self.objc_msg_send_value) |v| return v;
|
||||
const name_z = "objc_msgSend";
|
||||
if (c.LLVMGetNamedFunction(self.llvm_module, name_z)) |existing| {
|
||||
@@ -1411,492 +1411,10 @@ pub const LLVMEmitter = struct {
|
||||
.deref => |un| self.ops().emitDeref(instruction, un),
|
||||
|
||||
// ── Calls ─────────────────────────────────────────────
|
||||
.objc_msg_send => |msg| {
|
||||
const msg_send = self.getObjcMsgSendValue();
|
||||
// Detect the sret case: >16 B non-HFA struct return.
|
||||
// Same predicate as the plain-foreign-call path so the
|
||||
// two arms stay in lockstep.
|
||||
const raw_ret_ty = self.toLLVMType(instruction.ty);
|
||||
const uses_sret = self.needsByval(instruction.ty, raw_ret_ty);
|
||||
const ret_ty = if (uses_sret) self.cached_void else raw_ret_ty;
|
||||
|
||||
// Slot layout:
|
||||
// uses_sret = false → [recv, sel, args...]
|
||||
// uses_sret = true → [sret_slot, recv, sel, args...]
|
||||
const sret_off: usize = if (uses_sret) 1 else 0;
|
||||
const total_params: usize = 2 + msg.args.len + sret_off;
|
||||
const param_types = self.alloc.alloc(c.LLVMTypeRef, total_params) catch unreachable;
|
||||
defer self.alloc.free(param_types);
|
||||
const call_args = self.alloc.alloc(c.LLVMValueRef, total_params) catch unreachable;
|
||||
defer self.alloc.free(call_args);
|
||||
|
||||
var sret_slot: c.LLVMValueRef = null;
|
||||
if (uses_sret) {
|
||||
sret_slot = c.LLVMBuildAlloca(self.builder, raw_ret_ty, "objc.sret");
|
||||
param_types[0] = self.cached_ptr;
|
||||
call_args[0] = sret_slot;
|
||||
}
|
||||
|
||||
// recv (typed *void from the IR)
|
||||
param_types[sret_off] = self.cached_ptr;
|
||||
call_args[sret_off] = self.coerceArg(self.resolveRef(msg.recv), self.cached_ptr);
|
||||
// sel (loaded SEL — opaque ptr)
|
||||
param_types[sret_off + 1] = self.cached_ptr;
|
||||
call_args[sret_off + 1] = self.coerceArg(self.resolveRef(msg.sel), self.cached_ptr);
|
||||
// additional args take their IR types, with ABI
|
||||
// coercion applied so structs / strings decay the
|
||||
// same way they do for any C foreign call.
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
param_types[i + 2 + sret_off] = coerced_ty;
|
||||
call_args[i + 2 + sret_off] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
|
||||
const fn_ty = c.LLVMFunctionType(ret_ty, param_types.ptr, @intCast(total_params), 0);
|
||||
const call_label: [*:0]const u8 = if (instruction.ty == .void or uses_sret) "" else "objc.msg";
|
||||
var result = c.LLVMBuildCall2(self.builder, fn_ty, msg_send, call_args.ptr, @intCast(total_params), call_label);
|
||||
if (uses_sret) {
|
||||
// Tag the call's arg 0 (sret slot) with the sret
|
||||
// attribute so the AArch64 / SysV backends route
|
||||
// through the x8 / hidden-pointer convention.
|
||||
const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4);
|
||||
const sret_attr = c.LLVMCreateTypeAttribute(self.context, sret_kind, raw_ret_ty);
|
||||
const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1));
|
||||
c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr);
|
||||
result = c.LLVMBuildLoad2(self.builder, raw_ret_ty, sret_slot, "objc.sret.load");
|
||||
}
|
||||
// Always mapRef — the IR Ref counter for this
|
||||
// instruction advances regardless of return type,
|
||||
// so skipping it would misalign every subsequent
|
||||
// ref lookup in this function.
|
||||
self.mapRef(result);
|
||||
},
|
||||
.jni_msg_send => |msg| {
|
||||
// JNI vtable indirection:
|
||||
// ifs = *env // JNINativeInterface*
|
||||
// instance: cls = ifs[GetObjectClass](env, target)
|
||||
// mid = ifs[GetMethodID](env, cls, name, sig)
|
||||
// ifs[Call<T>Method](env, target, mid, args...)
|
||||
// static: target IS the jclass — skip GetObjectClass
|
||||
// mid = ifs[GetStaticMethodID](env, target, name, sig)
|
||||
// ifs[CallStatic<T>Method](env, target, mid, args...)
|
||||
// ctor: cls = ifs[FindClass](env, parent_class_path)
|
||||
// mid = ifs[GetMethodID](env, cls, "<init>", sig)
|
||||
// ifs[NewObject](env, cls, mid, args...) → jobject
|
||||
// nonvirt: handled below via FindClass + GetMethodID +
|
||||
// CallNonvirtual<T>Method.
|
||||
// The cached path (msg.cache_key != null) still shares one
|
||||
// (jclass GlobalRef, jmethodID) pair per literal (name, sig).
|
||||
if (msg.is_constructor) {
|
||||
self.emitJniConstructor(msg, instruction.ty);
|
||||
return;
|
||||
}
|
||||
const ret_ty_id = instruction.ty;
|
||||
const is_pointer_ret = switch (self.ir_mod.types.get(ret_ty_id)) {
|
||||
.pointer, .many_pointer => true,
|
||||
else => false,
|
||||
};
|
||||
const call_method_offset: u32 = if (msg.is_static) blk: {
|
||||
if (is_pointer_ret) break :blk Jni.CallStaticObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => Jni.CallStaticVoidMethod,
|
||||
.s32 => Jni.CallStaticIntMethod,
|
||||
.s64 => Jni.CallStaticLongMethod,
|
||||
.f32 => Jni.CallStaticFloatMethod,
|
||||
.f64 => Jni.CallStaticDoubleMethod,
|
||||
.bool => Jni.CallStaticBooleanMethod,
|
||||
else => {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
} else if (msg.is_nonvirtual) blk: {
|
||||
if (is_pointer_ret) break :blk Jni.CallNonvirtualObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => Jni.CallNonvirtualVoidMethod,
|
||||
.s32 => Jni.CallNonvirtualIntMethod,
|
||||
.s64 => Jni.CallNonvirtualLongMethod,
|
||||
.f32 => Jni.CallNonvirtualFloatMethod,
|
||||
.f64 => Jni.CallNonvirtualDoubleMethod,
|
||||
.bool => Jni.CallNonvirtualBooleanMethod,
|
||||
else => {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
} else blk: {
|
||||
if (is_pointer_ret) break :blk Jni.CallObjectMethod;
|
||||
break :blk switch (ret_ty_id) {
|
||||
.void => Jni.CallVoidMethod,
|
||||
.s32 => Jni.CallIntMethod,
|
||||
.s64 => Jni.CallLongMethod,
|
||||
.f32 => Jni.CallFloatMethod,
|
||||
.f64 => Jni.CallDoubleMethod,
|
||||
.bool => Jni.CallBooleanMethod,
|
||||
else => {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
},
|
||||
};
|
||||
};
|
||||
const get_mid_offset: u32 = if (msg.is_static) Jni.GetStaticMethodID else Jni.GetMethodID;
|
||||
|
||||
const env = self.resolveRef(msg.env);
|
||||
const target = self.resolveRef(msg.target);
|
||||
// String literals lower as `{ptr, i64}` slices in sx IR;
|
||||
// JNI's `GetMethodID` expects raw C strings, so extract
|
||||
// field 0 when the source is a slice.
|
||||
const name_ptr = self.extractSlicePtr(self.resolveRef(msg.name));
|
||||
const sig_ptr = self.extractSlicePtr(self.resolveRef(msg.sig));
|
||||
|
||||
const ifs = c.LLVMBuildLoad2(self.builder, self.cached_ptr, env, "jni.ifs");
|
||||
|
||||
// Method-ID resolution. When `name` and `sig` are both
|
||||
// string literals the call site participates in
|
||||
// `(name, sig)` slot interning (step 1.17): a shared
|
||||
// pair of static globals holds the `jclass` GlobalRef
|
||||
// and the `jmethodID`, populated lazily on the first
|
||||
// call to any matching site. Non-literal sites fall
|
||||
// back to the per-call `GetObjectClass + GetMethodID`
|
||||
// sequence (1.15 shape).
|
||||
const mid = if (msg.cache_key) |ck| blk: {
|
||||
const pair = self.ffiCtors().getOrCreateJniSlots(ck.name_str, ck.sig_str);
|
||||
const cached_mid = c.LLVMBuildLoad2(self.builder, self.cached_ptr, pair.mid_slot, "jni.cached.mid");
|
||||
const is_cached = c.LLVMBuildICmp(self.builder, c.LLVMIntNE, cached_mid, c.LLVMConstNull(self.cached_ptr), "jni.is.cached");
|
||||
|
||||
const cur_fn = c.LLVMGetBasicBlockParent(c.LLVMGetInsertBlock(self.builder));
|
||||
const miss_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "jni.miss");
|
||||
const cont_bb = c.LLVMAppendBasicBlockInContext(self.context, cur_fn, "jni.cont");
|
||||
const before_bb = c.LLVMGetInsertBlock(self.builder);
|
||||
_ = c.LLVMBuildCondBr(self.builder, is_cached, cont_bb, miss_bb);
|
||||
|
||||
// Miss path:
|
||||
// instance: GetObjectClass → NewGlobalRef → GetMethodID
|
||||
// static: target IS class → NewGlobalRef(target) → GetStaticMethodID
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, miss_bb);
|
||||
const local_cls = if (msg.is_static) target else inst_cls: {
|
||||
const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass");
|
||||
var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr };
|
||||
const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0);
|
||||
var gocls_args = [_]c.LLVMValueRef{ env, target };
|
||||
break :inst_cls c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls");
|
||||
};
|
||||
const new_global_ref = self.loadJniFn(ifs, Jni.NewGlobalRef, "jni.NewGlobalRef");
|
||||
var ngref_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr };
|
||||
const ngref_ty = c.LLVMFunctionType(self.cached_ptr, &ngref_params, 2, 0);
|
||||
var ngref_args = [_]c.LLVMValueRef{ env, local_cls };
|
||||
const global_cls = c.LLVMBuildCall2(self.builder, ngref_ty, new_global_ref, &ngref_args, 2, "jni.global.cls");
|
||||
_ = c.LLVMBuildStore(self.builder, global_cls, pair.cls_slot);
|
||||
const get_mid = self.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID");
|
||||
var gmid_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_ptr, self.cached_ptr };
|
||||
const gmid_ty = c.LLVMFunctionType(self.cached_ptr, &gmid_params, 4, 0);
|
||||
var gmid_args = [_]c.LLVMValueRef{ env, global_cls, name_ptr, sig_ptr };
|
||||
const fresh_mid = c.LLVMBuildCall2(self.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.fresh.mid");
|
||||
_ = c.LLVMBuildStore(self.builder, fresh_mid, pair.mid_slot);
|
||||
const miss_end_bb = c.LLVMGetInsertBlock(self.builder);
|
||||
_ = c.LLVMBuildBr(self.builder, cont_bb);
|
||||
|
||||
// Cont: phi the cached vs fresh mid.
|
||||
c.LLVMPositionBuilderAtEnd(self.builder, cont_bb);
|
||||
const phi = c.LLVMBuildPhi(self.builder, self.cached_ptr, "jni.mid");
|
||||
var phi_vals = [_]c.LLVMValueRef{ cached_mid, fresh_mid };
|
||||
var phi_blocks = [_]c.LLVMBasicBlockRef{ before_bb, miss_end_bb };
|
||||
c.LLVMAddIncoming(phi, &phi_vals, &phi_blocks, 2);
|
||||
break :blk phi;
|
||||
} else blk: {
|
||||
const cls = if (msg.is_static) target else if (msg.is_nonvirtual) nonvirt_cls: {
|
||||
// `super.method(args)`: dispatch is bound to a
|
||||
// specific class (the parent), not subclass-override.
|
||||
// Resolve via FindClass(parent_path). No caching yet —
|
||||
// per-call lookup. The parent path is a NUL-terminated
|
||||
// C string emitted as a private LLVM global.
|
||||
const path = msg.parent_class_path orelse "";
|
||||
const path_global = self.emitCStringGlobal(path, "jni.parent.path");
|
||||
const find_class = self.loadJniFn(ifs, Jni.FindClass, "jni.FindClass");
|
||||
var fc_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr };
|
||||
const fc_ty = c.LLVMFunctionType(self.cached_ptr, &fc_params, 2, 0);
|
||||
var fc_args = [_]c.LLVMValueRef{ env, path_global };
|
||||
break :nonvirt_cls c.LLVMBuildCall2(self.builder, fc_ty, find_class, &fc_args, 2, "jni.parent.cls");
|
||||
} else inst_cls: {
|
||||
const get_obj_cls = self.loadJniFn(ifs, Jni.GetObjectClass, "jni.GetObjectClass");
|
||||
var gocls_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr };
|
||||
const gocls_ty = c.LLVMFunctionType(self.cached_ptr, &gocls_params, 2, 0);
|
||||
var gocls_args = [_]c.LLVMValueRef{ env, target };
|
||||
break :inst_cls c.LLVMBuildCall2(self.builder, gocls_ty, get_obj_cls, &gocls_args, 2, "jni.cls");
|
||||
};
|
||||
const get_mid = self.loadJniFn(ifs, get_mid_offset, if (msg.is_static) "jni.GetStaticMethodID" else "jni.GetMethodID");
|
||||
var gmid_params = [_]c.LLVMTypeRef{ self.cached_ptr, self.cached_ptr, self.cached_ptr, self.cached_ptr };
|
||||
const gmid_ty = c.LLVMFunctionType(self.cached_ptr, &gmid_params, 4, 0);
|
||||
var gmid_args = [_]c.LLVMValueRef{ env, cls, name_ptr, sig_ptr };
|
||||
const mid_val = c.LLVMBuildCall2(self.builder, gmid_ty, get_mid, &gmid_args, 4, "jni.mid");
|
||||
if (msg.is_nonvirtual) {
|
||||
// Stash cls in a dummy slot so the call site below
|
||||
// can pick it up. Easiest path: do the call right
|
||||
// here and return Ref.none, but we need to keep the
|
||||
// outer phi shape. Instead, return both via tuple
|
||||
// through an auxiliary local — simplest is to attach
|
||||
// `cls` to a per-invocation slot. Use a stack alloca.
|
||||
const cls_slot = c.LLVMBuildAlloca(self.builder, self.cached_ptr, "jni.parent.cls.slot");
|
||||
_ = c.LLVMBuildStore(self.builder, cls, cls_slot);
|
||||
// Tag the slot pointer onto the phi result via the
|
||||
// generated metadata: we'll re-extract by re-running
|
||||
// FindClass — actually simpler: lower nonvirtual on
|
||||
// the spot below. Drop the implicit `break` here:
|
||||
const call_fn = self.loadJniFn(ifs, call_method_offset, "jni.callfn.nonvirtual");
|
||||
const raw_ret = self.toLLVMType(ret_ty_id);
|
||||
const total_call_params_nv: usize = 4 + msg.args.len;
|
||||
const call_param_types_nv = self.alloc.alloc(c.LLVMTypeRef, total_call_params_nv) catch unreachable;
|
||||
defer self.alloc.free(call_param_types_nv);
|
||||
const call_args_nv = self.alloc.alloc(c.LLVMValueRef, total_call_params_nv) catch unreachable;
|
||||
defer self.alloc.free(call_args_nv);
|
||||
call_param_types_nv[0] = self.cached_ptr;
|
||||
call_param_types_nv[1] = self.cached_ptr;
|
||||
call_param_types_nv[2] = self.cached_ptr;
|
||||
call_param_types_nv[3] = self.cached_ptr;
|
||||
call_args_nv[0] = env;
|
||||
call_args_nv[1] = target;
|
||||
call_args_nv[2] = cls;
|
||||
call_args_nv[3] = mid_val;
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
call_param_types_nv[i + 4] = coerced_ty;
|
||||
call_args_nv[i + 4] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
const call_fn_ty_nv = c.LLVMFunctionType(raw_ret, call_param_types_nv.ptr, @intCast(total_call_params_nv), 0);
|
||||
const label_nv: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.nonvirtual.ret";
|
||||
const result_nv = c.LLVMBuildCall2(self.builder, call_fn_ty_nv, call_fn, call_args_nv.ptr, @intCast(total_call_params_nv), label_nv);
|
||||
self.mapRef(result_nv);
|
||||
return;
|
||||
}
|
||||
break :blk mid_val;
|
||||
};
|
||||
|
||||
// Call<Type>Method: (JNIEnv*, jobject, jmethodID, args...) -> RetTy
|
||||
const call_fn = self.loadJniFn(ifs, call_method_offset, "jni.callfn");
|
||||
const raw_ret = self.toLLVMType(ret_ty_id);
|
||||
const total_call_params: usize = 3 + msg.args.len;
|
||||
const call_param_types = self.alloc.alloc(c.LLVMTypeRef, total_call_params) catch unreachable;
|
||||
defer self.alloc.free(call_param_types);
|
||||
const call_args = self.alloc.alloc(c.LLVMValueRef, total_call_params) catch unreachable;
|
||||
defer self.alloc.free(call_args);
|
||||
call_param_types[0] = self.cached_ptr;
|
||||
call_param_types[1] = self.cached_ptr;
|
||||
call_param_types[2] = self.cached_ptr;
|
||||
call_args[0] = env;
|
||||
call_args[1] = target;
|
||||
call_args[2] = mid;
|
||||
for (msg.args, 0..) |arg_ref, i| {
|
||||
const raw_ty = self.getRefIRType(arg_ref) orelse .void;
|
||||
const raw_llvm = self.toLLVMType(raw_ty);
|
||||
const coerced_ty = self.abiCoerceParamType(raw_ty, raw_llvm);
|
||||
call_param_types[i + 3] = coerced_ty;
|
||||
call_args[i + 3] = self.coerceArg(self.resolveRef(arg_ref), coerced_ty);
|
||||
}
|
||||
const call_fn_ty = c.LLVMFunctionType(raw_ret, call_param_types.ptr, @intCast(total_call_params), 0);
|
||||
const label: [*:0]const u8 = if (ret_ty_id == .void) "" else "jni.ret";
|
||||
const result = c.LLVMBuildCall2(self.builder, call_fn_ty, call_fn, call_args.ptr, @intCast(total_call_params), label);
|
||||
self.mapRef(result);
|
||||
},
|
||||
.call => |call_op| {
|
||||
// Evaluate comptime functions at compile time
|
||||
const callee_func = &self.ir_mod.functions.items[call_op.callee.index()];
|
||||
if (callee_func.is_comptime and call_op.args.len == 0) {
|
||||
var interp_inst = Interpreter.init(self.ir_mod, self.alloc);
|
||||
interp_inst.build_config = &self.build_config;
|
||||
if (self.import_sources) |sm| interp_inst.setSourceMap(sm);
|
||||
defer interp_inst.deinit();
|
||||
if (interp_inst.call(call_op.callee, &.{})) |result| {
|
||||
if (result.asInt()) |v| {
|
||||
self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @bitCast(v), 0));
|
||||
return;
|
||||
} else if (result.asFloat()) |v| {
|
||||
self.mapRef(c.LLVMConstReal(self.toLLVMType(instruction.ty), v));
|
||||
return;
|
||||
} else if (result.asBool()) |v| {
|
||||
self.mapRef(c.LLVMConstInt(self.toLLVMType(instruction.ty), @intFromBool(v), 0));
|
||||
return;
|
||||
} else if (result == .string) {
|
||||
self.mapRef(self.emitStringConstant(result.string));
|
||||
return;
|
||||
}
|
||||
} else |_| {}
|
||||
}
|
||||
const callee = self.func_map.get(call_op.callee.index()) orelse {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
};
|
||||
const callee_needs_c_abi = callee_func.is_extern or callee_func.call_conv == .c;
|
||||
const callee_raw_ret = self.toLLVMType(callee_func.ret);
|
||||
const callee_uses_sret = callee_needs_c_abi and self.needsByval(callee_func.ret, callee_raw_ret);
|
||||
|
||||
// When the callee uses sret, prepend an alloca for the result.
|
||||
// Index alignment: actual_args[0] = sret_slot; actual_args[i+1] = sx arg i.
|
||||
const sret_off: usize = if (callee_uses_sret) 1 else 0;
|
||||
const total_args = call_op.args.len + sret_off;
|
||||
const args = self.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable;
|
||||
defer self.alloc.free(args);
|
||||
var sret_slot: c.LLVMValueRef = null;
|
||||
if (callee_uses_sret) {
|
||||
sret_slot = c.LLVMBuildAlloca(self.builder, callee_raw_ret, "sret.slot");
|
||||
args[0] = sret_slot;
|
||||
}
|
||||
for (call_op.args, 0..) |arg_ref, j| {
|
||||
args[j + sret_off] = self.resolveRef(arg_ref);
|
||||
}
|
||||
const arg_count: c_uint = @intCast(total_args);
|
||||
|
||||
// Get the function type from LLVM and coerce arguments
|
||||
const fn_ty = c.LLVMGlobalGetValueType(callee);
|
||||
const param_count = c.LLVMCountParamTypes(fn_ty);
|
||||
if (param_count > 0) {
|
||||
const param_types = self.alloc.alloc(c.LLVMTypeRef, param_count) catch unreachable;
|
||||
defer self.alloc.free(param_types);
|
||||
c.LLVMGetParamTypes(fn_ty, param_types.ptr);
|
||||
for (0..@min(args.len, param_count)) |j| {
|
||||
// The sret slot is already a properly-typed pointer; skip coercion.
|
||||
if (callee_uses_sret and j == 0) continue;
|
||||
const fn_param_idx = j - sret_off;
|
||||
// Materialize byval args before coercion so we pass a ptr instead of the struct value.
|
||||
if (callee_needs_c_abi and fn_param_idx < callee_func.params.len) {
|
||||
const ir_ty = callee_func.params[fn_param_idx].ty;
|
||||
const raw_struct = self.toLLVMType(ir_ty);
|
||||
if (self.needsByval(ir_ty, raw_struct)) {
|
||||
args[j] = self.materializeByvalArg(args[j], raw_struct);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
args[j] = self.coerceArg(args[j], param_types[j]);
|
||||
}
|
||||
}
|
||||
// A `void`/`noreturn` call has no value, so it must stay
|
||||
// unnamed (LLVM rejects a named void result).
|
||||
const call_is_void_like = instruction.ty == .void or instruction.ty == .noreturn;
|
||||
const call_label: [*:0]const u8 = if (call_is_void_like or callee_uses_sret) "" else "call";
|
||||
var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, call_label);
|
||||
if (callee_uses_sret) {
|
||||
// Mirror the function-decl `sret(<T>)` attribute on the call site so the
|
||||
// LLVM backend lowers arg 0 via x8 (AAPCS64) / hidden ptr (SysV AMD64).
|
||||
const sret_kind = c.LLVMGetEnumAttributeKindForName("sret", 4);
|
||||
const sret_attr = c.LLVMCreateTypeAttribute(self.context, sret_kind, callee_raw_ret);
|
||||
const param1_idx: c.LLVMAttributeIndex = @bitCast(@as(i32, 1));
|
||||
c.LLVMAddCallSiteAttribute(result, param1_idx, sret_attr);
|
||||
// Load the actual struct value the callee wrote into the slot.
|
||||
result = c.LLVMBuildLoad2(self.builder, callee_raw_ret, sret_slot, "sret.load");
|
||||
} else if (!call_is_void_like and callee_func.is_extern) {
|
||||
// Coerce ABI return value (e.g. i64 / [2 x i64]) back to IR struct type if needed
|
||||
const expected_ty = self.toLLVMType(instruction.ty);
|
||||
result = self.coerceArg(result, expected_ty);
|
||||
}
|
||||
self.mapRef(result);
|
||||
},
|
||||
.call_indirect => |call_op| {
|
||||
const callee = self.resolveRef(call_op.callee);
|
||||
const arg_count: c_uint = @intCast(call_op.args.len);
|
||||
const args = self.alloc.alloc(c.LLVMValueRef, call_op.args.len) catch unreachable;
|
||||
defer self.alloc.free(args);
|
||||
for (call_op.args, 0..) |arg_ref, j| {
|
||||
args[j] = self.resolveRef(arg_ref);
|
||||
}
|
||||
|
||||
// Get callee's IR type to resolve parameter types accurately
|
||||
const callee_ir_ty = self.getRefIRType(call_op.callee);
|
||||
const fn_params: ?[]const @import("types.zig").TypeId = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.ir_mod.types.get(cty);
|
||||
switch (ci) {
|
||||
.function => |f| break :blk f.params,
|
||||
.closure => |cl| break :blk cl.params,
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
break :blk null;
|
||||
} else null;
|
||||
|
||||
// Read the fn-pointer type's calling convention. Only `.c` opts
|
||||
// into the C-ABI byval coercion for >16B aggregate params.
|
||||
const fp_is_c_abi: bool = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.ir_mod.types.get(cty);
|
||||
if (ci == .function and ci.function.call_conv == .c) break :blk true;
|
||||
}
|
||||
break :blk false;
|
||||
} else false;
|
||||
|
||||
// Default-conv fn-pointers under implicit-ctx carry a hidden
|
||||
// `*void` (the implicit __sx_ctx) at LLVM slot 0. The IR fn
|
||||
// type does not include it, so shift fn_params lookups by 1.
|
||||
const fp_ctx_slots: usize = if (callee_ir_ty) |cty| blk: {
|
||||
if (!self.ir_mod.has_implicit_ctx) break :blk 0;
|
||||
if (cty.isBuiltin()) break :blk 0;
|
||||
const ci = self.ir_mod.types.get(cty);
|
||||
switch (ci) {
|
||||
.function => |f| break :blk if (f.call_conv == .c) @as(usize, 0) else 1,
|
||||
else => break :blk 0,
|
||||
}
|
||||
} else 0;
|
||||
|
||||
const ret_ty = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.ir_mod.types.get(cty);
|
||||
switch (ci) {
|
||||
.function => |f| break :blk self.toLLVMType(f.ret),
|
||||
.closure => |cl| break :blk self.toLLVMType(cl.ret),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
break :blk self.toLLVMType(instruction.ty);
|
||||
} else self.toLLVMType(instruction.ty);
|
||||
|
||||
const param_tys = self.alloc.alloc(c.LLVMTypeRef, call_op.args.len) catch unreachable;
|
||||
defer self.alloc.free(param_tys);
|
||||
if (fn_params) |fp| {
|
||||
for (0..call_op.args.len) |j| {
|
||||
// Slots 0..fp_ctx_slots are the implicit __sx_ctx
|
||||
// (passed as opaque ptr; not in fp).
|
||||
if (j < fp_ctx_slots) {
|
||||
param_tys[j] = self.cached_ptr;
|
||||
args[j] = self.coerceArg(args[j], self.cached_ptr);
|
||||
continue;
|
||||
}
|
||||
const fp_idx = j - fp_ctx_slots;
|
||||
if (fp_idx < fp.len) {
|
||||
const raw_struct = self.toLLVMType(fp[fp_idx]);
|
||||
if (fp_is_c_abi and self.needsByval(fp[fp_idx], raw_struct)) {
|
||||
args[j] = self.materializeByvalArg(args[j], raw_struct);
|
||||
param_tys[j] = self.cached_ptr;
|
||||
continue;
|
||||
}
|
||||
var llvm_pty = raw_struct;
|
||||
// Array params in fn-ptr calls decay to pointers (C ABI)
|
||||
if (c.LLVMGetTypeKind(llvm_pty) == c.LLVMArrayTypeKind) {
|
||||
llvm_pty = self.cached_ptr;
|
||||
}
|
||||
param_tys[j] = llvm_pty;
|
||||
args[j] = self.coerceArg(args[j], llvm_pty);
|
||||
} else {
|
||||
param_tys[j] = c.LLVMTypeOf(args[j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (args, 0..) |arg, j| {
|
||||
param_tys[j] = c.LLVMTypeOf(arg);
|
||||
}
|
||||
}
|
||||
const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, arg_count, 0);
|
||||
const icall_void_like = instruction.ty == .void or instruction.ty == .noreturn;
|
||||
var result = c.LLVMBuildCall2(self.builder, fn_ty, callee, args.ptr, arg_count, if (icall_void_like) "" else "icall");
|
||||
|
||||
// Coerce call result to instruction's expected type
|
||||
const expected_ty = self.toLLVMType(instruction.ty);
|
||||
if (!icall_void_like and c.LLVMTypeOf(result) != expected_ty) {
|
||||
result = self.coerceArg(result, expected_ty);
|
||||
}
|
||||
self.mapRef(result);
|
||||
},
|
||||
.objc_msg_send => |msg| self.ops().emitObjcMsgSend(instruction, msg),
|
||||
.jni_msg_send => |msg| self.ops().emitJniMsgSend(instruction, msg),
|
||||
.call => |call_op| self.ops().emitCall(instruction, call_op),
|
||||
.call_indirect => |call_op| self.ops().emitCallIndirect(instruction, call_op),
|
||||
|
||||
// ── Terminators ────────────────────────────────────────
|
||||
.ret => |un| {
|
||||
@@ -2400,196 +1918,9 @@ pub const LLVMEmitter = struct {
|
||||
},
|
||||
|
||||
// ── Call extensions ───────────────────────────────────────
|
||||
.call_builtin => |bi| {
|
||||
// Builtins that map to libc functions or LLVM intrinsics
|
||||
switch (bi.builtin) {
|
||||
.sqrt, .sin, .cos, .floor => {
|
||||
const val = self.resolveRef(bi.args[0]);
|
||||
const val_ty = c.LLVMTypeOf(val);
|
||||
const val_kind = c.LLVMGetTypeKind(val_ty);
|
||||
if (val_kind == c.LLVMFloatTypeKind) {
|
||||
const f = self.getOrDeclareMathF32(bi.builtin);
|
||||
var args = [_]c.LLVMValueRef{val};
|
||||
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF32Type(), f, &args, 1, @tagName(bi.builtin)));
|
||||
} else {
|
||||
const coerced = if (val_kind != c.LLVMDoubleTypeKind) self.coerceArg(val, self.cached_f64) else val;
|
||||
const f = self.getOrDeclareMathF64(bi.builtin);
|
||||
var args = [_]c.LLVMValueRef{coerced};
|
||||
self.mapRef(c.LLVMBuildCall2(self.builder, self.getMathF64Type(), f, &args, 1, @tagName(bi.builtin)));
|
||||
}
|
||||
},
|
||||
.out => {
|
||||
// out(str): extract ptr and len from string fat pointer, call write(1, ptr, len)
|
||||
const str_val = self.resolveRef(bi.args[0]);
|
||||
const raw_ptr = c.LLVMBuildExtractValue(self.builder, str_val, 0, "str.ptr");
|
||||
const str_len = c.LLVMBuildExtractValue(self.builder, str_val, 1, "str.len");
|
||||
// On wasm32, count param is i32 (size_t)
|
||||
const count = if (self.target_config.isWasm32())
|
||||
c.LLVMBuildTrunc(self.builder, str_len, self.cached_i32, "len.tr")
|
||||
else
|
||||
str_len;
|
||||
const write_fn = self.getOrDeclareWrite();
|
||||
var write_args = [_]c.LLVMValueRef{
|
||||
c.LLVMConstInt(self.cached_i32, 1, 0), // fd = stdout
|
||||
raw_ptr,
|
||||
count,
|
||||
};
|
||||
_ = c.LLVMBuildCall2(self.builder, self.getWriteType(), write_fn, &write_args, 3, "");
|
||||
self.advanceRefCounter();
|
||||
},
|
||||
.type_name => {
|
||||
// Dynamic `type_name(t)` at runtime: extract
|
||||
// the TypeId from the arg (an Any-boxed Type
|
||||
// value: tag=`.s64.index()`, value=tid), GEP
|
||||
// into the compiler-emitted `__sx_type_names`
|
||||
// global, load the string. The arg's LLVM
|
||||
// shape is the `{i64, i64}` Any aggregate
|
||||
// (because the IR-side arg type is `.any`
|
||||
// when boxed); for unboxed direct call sites
|
||||
// (the arg IR type is `.s64` from
|
||||
// `const_type`), the value IS the TypeId
|
||||
// index directly.
|
||||
const arg_ref = bi.args[0];
|
||||
const arg_val = self.resolveRef(arg_ref);
|
||||
const arg_ir_ty = self.getRefIRType(arg_ref) orelse @import("types.zig").TypeId.s64;
|
||||
const tid_idx = blk: {
|
||||
if (arg_ir_ty == .any) {
|
||||
// Boxed: extract value field.
|
||||
break :blk c.LLVMBuildExtractValue(self.builder, arg_val, 1, "tn.tid");
|
||||
}
|
||||
// Bare i64 (TypeId index).
|
||||
break :blk arg_val;
|
||||
};
|
||||
const arr_global = self.reflection().getOrBuildTypeNameArray();
|
||||
const arr_len = self.type_name_array_len;
|
||||
const string_ty = self.getStringStructType();
|
||||
const arr_ty = c.LLVMArrayType(string_ty, arr_len);
|
||||
const zero = c.LLVMConstInt(self.cached_i64, 0, 0);
|
||||
var indices = [2]c.LLVMValueRef{ zero, tid_idx };
|
||||
const gep = c.LLVMBuildInBoundsGEP2(self.builder, arr_ty, arr_global, &indices, 2, "tn.gep");
|
||||
const result = c.LLVMBuildLoad2(self.builder, string_ty, gep, "tn.load");
|
||||
self.mapRef(result);
|
||||
},
|
||||
.type_eq => {
|
||||
// Dynamic `type_eq(a, b)` — both args are
|
||||
// Type values. Extract TypeId from each Any
|
||||
// box (or use directly if `.s64`-typed),
|
||||
// icmp eq.
|
||||
const a = blk: {
|
||||
const v = self.resolveRef(bi.args[0]);
|
||||
const ty = self.getRefIRType(bi.args[0]) orelse @import("types.zig").TypeId.s64;
|
||||
if (ty == .any) break :blk c.LLVMBuildExtractValue(self.builder, v, 1, "te.a");
|
||||
break :blk v;
|
||||
};
|
||||
const b = blk: {
|
||||
const v = self.resolveRef(bi.args[1]);
|
||||
const ty = self.getRefIRType(bi.args[1]) orelse @import("types.zig").TypeId.s64;
|
||||
if (ty == .any) break :blk c.LLVMBuildExtractValue(self.builder, v, 1, "te.b");
|
||||
break :blk v;
|
||||
};
|
||||
const eq_res = c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, a, b, "te.eq");
|
||||
self.mapRef(eq_res);
|
||||
},
|
||||
.has_impl => {
|
||||
// Runtime has_impl needs a protocol-map
|
||||
// snapshot — not wired yet. Silent false for
|
||||
// now; the lower-time fold via
|
||||
// `tryConstBoolCondition` covers every
|
||||
// statically-resolvable call.
|
||||
self.mapRef(c.LLVMConstInt(self.cached_i1, 0, 0));
|
||||
},
|
||||
else => {
|
||||
// size_of, cast — handled by lowering or codegen glue
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
},
|
||||
}
|
||||
},
|
||||
.compiler_call => {
|
||||
// Compiler hooks are comptime-only; if one reaches emission, produce undef
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
},
|
||||
.call_closure => |call_op| {
|
||||
// Closure: { fn_ptr, env }.
|
||||
//
|
||||
// ABI (when module.has_implicit_ctx):
|
||||
// trampoline signature: (__sx_ctx, env, args...)
|
||||
// call_op.args[0] = __sx_ctx (prepended by lowering)
|
||||
// call_op.args[1..] = user args
|
||||
// extracted env_ptr = inserted at LLVM slot 1
|
||||
//
|
||||
// ABI (without implicit_ctx):
|
||||
// trampoline signature: (env, args...)
|
||||
// call_op.args = user args (no ctx prepend)
|
||||
// extracted env_ptr = inserted at LLVM slot 0
|
||||
const closure = self.resolveRef(call_op.callee);
|
||||
const cl_kind = c.LLVMGetTypeKind(c.LLVMTypeOf(closure));
|
||||
if (cl_kind != c.LLVMStructTypeKind) {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
return;
|
||||
}
|
||||
const fn_ptr = c.LLVMBuildExtractValue(self.builder, closure, 0, "cl.fn");
|
||||
const env_ptr = c.LLVMBuildExtractValue(self.builder, closure, 1, "cl.env");
|
||||
|
||||
// Get the closure's declared parameter types from the IR type system
|
||||
const callee_ir_ty = self.getRefIRType(call_op.callee);
|
||||
const closure_params: ?[]const @import("types.zig").TypeId = if (callee_ir_ty) |cty| blk: {
|
||||
if (!cty.isBuiltin()) {
|
||||
const ci = self.ir_mod.types.get(cty);
|
||||
if (ci == .closure) break :blk ci.closure.params;
|
||||
}
|
||||
break :blk null;
|
||||
} else null;
|
||||
|
||||
const has_ctx = self.ir_mod.has_implicit_ctx;
|
||||
const user_args_offset_in_op: usize = if (has_ctx) 1 else 0;
|
||||
const user_args_count: usize = call_op.args.len -| user_args_offset_in_op;
|
||||
const ctx_slots: usize = if (has_ctx) 1 else 0;
|
||||
const total_args = ctx_slots + 1 + user_args_count; // [ctx?] + env + user_args
|
||||
|
||||
const args = self.alloc.alloc(c.LLVMValueRef, total_args) catch unreachable;
|
||||
defer self.alloc.free(args);
|
||||
if (has_ctx) {
|
||||
args[0] = self.resolveRef(call_op.args[0]); // ctx
|
||||
}
|
||||
args[ctx_slots] = env_ptr;
|
||||
for (0..user_args_count) |j| {
|
||||
args[ctx_slots + 1 + j] = self.resolveRef(call_op.args[user_args_offset_in_op + j]);
|
||||
}
|
||||
|
||||
// Build function type using declared param types (not arg types).
|
||||
// closure_params is user-visible (no ctx, no env), so they line
|
||||
// up with args[ctx_slots+1..].
|
||||
const ret_ty = self.toLLVMType(instruction.ty);
|
||||
const param_tys = self.alloc.alloc(c.LLVMTypeRef, total_args) catch unreachable;
|
||||
defer self.alloc.free(param_tys);
|
||||
if (has_ctx) param_tys[0] = self.cached_ptr; // __sx_ctx
|
||||
param_tys[ctx_slots] = self.cached_ptr; // env
|
||||
if (closure_params) |cp| {
|
||||
for (0..user_args_count) |j| {
|
||||
const param_ir_ty = if (j < cp.len) cp[j] else null;
|
||||
if (param_ir_ty) |pty| {
|
||||
const llvm_pty = self.toLLVMType(pty);
|
||||
param_tys[ctx_slots + 1 + j] = llvm_pty;
|
||||
args[ctx_slots + 1 + j] = self.coerceArg(args[ctx_slots + 1 + j], llvm_pty);
|
||||
} else {
|
||||
param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (0..user_args_count) |j| {
|
||||
param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]);
|
||||
}
|
||||
}
|
||||
const fn_ty = c.LLVMFunctionType(ret_ty, param_tys.ptr, @intCast(total_args), 0);
|
||||
|
||||
const is_void = instruction.ty == .void;
|
||||
const result = c.LLVMBuildCall2(self.builder, fn_ty, fn_ptr, args.ptr, @intCast(total_args), if (is_void) "" else "ccall");
|
||||
if (!is_void) {
|
||||
self.mapRef(result);
|
||||
} else {
|
||||
self.advanceRefCounter();
|
||||
}
|
||||
},
|
||||
.call_builtin => |bi| self.ops().emitCallBuiltin(instruction, bi),
|
||||
.compiler_call => self.ops().emitCompilerCall(instruction),
|
||||
.call_closure => |call_op| self.ops().emitCallClosure(instruction, call_op),
|
||||
|
||||
// ── Tuple ops ────────────────────────────────────────────
|
||||
.tuple_init => |agg| {
|
||||
@@ -3244,7 +2575,7 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMFunctionType(self.cached_ptr, ¶m_types, 3, 0);
|
||||
}
|
||||
|
||||
fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
||||
pub fn getOrDeclareMathF64(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
||||
const name: [*:0]const u8 = switch (id) {
|
||||
.sqrt => "sqrt",
|
||||
.sin => "sin",
|
||||
@@ -3256,12 +2587,12 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMAddFunction(self.llvm_module, name, self.getMathF64Type());
|
||||
}
|
||||
|
||||
fn getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
pub fn getMathF64Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
var param_types = [_]c.LLVMTypeRef{self.cached_f64};
|
||||
return c.LLVMFunctionType(self.cached_f64, ¶m_types, 1, 0);
|
||||
}
|
||||
|
||||
fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
||||
pub fn getOrDeclareMathF32(self: *LLVMEmitter, id: ir_inst.BuiltinId) c.LLVMValueRef {
|
||||
const name: [*:0]const u8 = switch (id) {
|
||||
.sqrt => "sqrtf",
|
||||
.sin => "sinf",
|
||||
@@ -3273,7 +2604,7 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMAddFunction(self.llvm_module, name, self.getMathF32Type());
|
||||
}
|
||||
|
||||
fn getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
pub fn getMathF32Type(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
var param_types = [_]c.LLVMTypeRef{self.cached_f32};
|
||||
return c.LLVMFunctionType(self.cached_f32, ¶m_types, 1, 0);
|
||||
}
|
||||
@@ -3286,12 +2617,12 @@ pub const LLVMEmitter = struct {
|
||||
return c.LLVMAddFunction(self.llvm_module, "memcmp", fn_ty);
|
||||
}
|
||||
|
||||
fn getOrDeclareWrite(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
pub fn getOrDeclareWrite(self: *LLVMEmitter) c.LLVMValueRef {
|
||||
if (c.LLVMGetNamedFunction(self.llvm_module, "write")) |f| return f;
|
||||
return c.LLVMAddFunction(self.llvm_module, "write", self.getWriteType());
|
||||
}
|
||||
|
||||
fn getWriteType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
pub fn getWriteType(self: *LLVMEmitter) c.LLVMTypeRef {
|
||||
// write(fd: i32, buf: ptr, count: size_t) → ssize_t
|
||||
const st = self.sizeType();
|
||||
var param_types = [_]c.LLVMTypeRef{ self.cached_i32, self.cached_ptr, st };
|
||||
@@ -3628,7 +2959,7 @@ pub const LLVMEmitter = struct {
|
||||
return .{ .e = self };
|
||||
}
|
||||
|
||||
fn ffiCtors(self: *LLVMEmitter) llvm_ffi_ctors.FfiCtors {
|
||||
pub fn ffiCtors(self: *LLVMEmitter) llvm_ffi_ctors.FfiCtors {
|
||||
return .{ .e = self };
|
||||
}
|
||||
|
||||
@@ -3659,7 +2990,7 @@ pub const LLVMEmitter = struct {
|
||||
return self.abiLowering().needsByval(ir_ty, raw_llvm_ty);
|
||||
}
|
||||
|
||||
fn materializeByvalArg(self: *LLVMEmitter, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
||||
pub fn materializeByvalArg(self: *LLVMEmitter, val: c.LLVMValueRef, struct_ty: c.LLVMTypeRef) c.LLVMValueRef {
|
||||
return self.abiLowering().materializeByvalArg(val, struct_ty);
|
||||
}
|
||||
|
||||
@@ -3777,7 +3108,7 @@ pub const LLVMEmitter = struct {
|
||||
/// Emit a NUL-terminated C string as a private LLVM global and return
|
||||
/// the pointer to its first byte. Used for FindClass(env, "<path>") etc.
|
||||
/// where the runtime expects raw `const char *`, not the sx slice shape.
|
||||
fn emitCStringGlobal(self: *LLVMEmitter, str: []const u8, name: [*:0]const u8) c.LLVMValueRef {
|
||||
pub fn emitCStringGlobal(self: *LLVMEmitter, str: []const u8, name: [*:0]const u8) c.LLVMValueRef {
|
||||
const z = self.alloc.dupeZ(u8, str) catch unreachable;
|
||||
defer self.alloc.free(z);
|
||||
return c.LLVMBuildGlobalStringPtr(self.builder, z.ptr, name);
|
||||
@@ -3787,7 +3118,7 @@ pub const LLVMEmitter = struct {
|
||||
/// `FindClass(env, parent_class_path)` → `GetMethodID(env, clazz,
|
||||
/// "<init>", sig)` → `NewObject(env, clazz, mid, args...)`. Returns
|
||||
/// the new jobject. Per-call lookups — no caching yet.
|
||||
fn emitJniConstructor(self: *LLVMEmitter, msg: ir_inst.JniMsgSend, ret_ty_id: TypeId) void {
|
||||
pub fn emitJniConstructor(self: *LLVMEmitter, msg: ir_inst.JniMsgSend, ret_ty_id: TypeId) void {
|
||||
const env = self.resolveRef(msg.env);
|
||||
const sig_ptr = self.extractSlicePtr(self.resolveRef(msg.sig));
|
||||
const name_ptr = self.extractSlicePtr(self.resolveRef(msg.name));
|
||||
|
||||
Reference in New Issue
Block a user