mem: Step 3 — thread __sx_ctx through closure/fn-pointer/method dispatch
Continues the implicit-Context refactor. Bare-fn trampolines, lambda trampolines, and protocol thunks now carry __sx_ctx at slot 0; call sites for closures, fn-pointer variables, and method dispatch prepend the caller's current ctx. - emit_llvm.zig:1687 call_indirect treats `fp_ctx_slots` leading args as opaque ptr (the implicit ctx) when the fn-pointer is default-conv under has_implicit_ctx. - lower.zig:fnPtrTypeWantsCtx predicate gates the prepend at both scope-local and global fn-pointer call sites. - lower.zig:fixupMethodReceiver skips __sx_ctx when probing the receiver param's type. - lower.zig:lowerLambda builds closure type from user-visible params only (skip ctx + env). - lower.zig:closure(bare_fn) builds closure type from user-visible params only. - module.zig: Module.has_implicit_ctx flag mirrors Lowering's switch so emit_llvm can read it without a back-pointer. Tests updated: - 5 ObjC-block/runtime tests get `callconv(.c)` on fn-ptr types cast from `objc_msgSend` / Block.invoke (C-side calls into sx). - ffi-06-callback gets `callconv(.c)` on double_it/add_with_ctx — the registered C-side callbacks. - 08-types snapshot regen (undefined-init drift from layout shift). - 11 JNI/ObjC .ir snapshots regen for the ctx-prepended thunk signatures. 151/152 example tests pass. Remaining failure (05-run) is the comptime/interp path that requires Step 7 (callWithDefaultContext).
This commit is contained in:
@@ -1717,6 +1717,19 @@ pub const LLVMEmitter = struct {
|
||||
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);
|
||||
@@ -1733,9 +1746,17 @@ pub const LLVMEmitter = struct {
|
||||
defer self.alloc.free(param_tys);
|
||||
if (fn_params) |fp| {
|
||||
for (0..call_op.args.len) |j| {
|
||||
if (j < fp.len) {
|
||||
const raw_struct = self.toLLVMType(fp[j]);
|
||||
if (fp_is_c_abi and self.needsByval(fp[j], raw_struct)) {
|
||||
// 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;
|
||||
@@ -2294,7 +2315,18 @@ pub const LLVMEmitter = struct {
|
||||
self.mapRef(c.LLVMGetUndef(self.toLLVMType(instruction.ty)));
|
||||
},
|
||||
.call_closure => |call_op| {
|
||||
// Closure: { fn_ptr, env } — extract fn_ptr, prepend env as first arg
|
||||
// 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) {
|
||||
@@ -2314,36 +2346,44 @@ pub const LLVMEmitter = struct {
|
||||
break :blk null;
|
||||
} else null;
|
||||
|
||||
// Build args: env_ptr + call args
|
||||
const total_args = call_op.args.len + 1;
|
||||
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);
|
||||
args[0] = env_ptr;
|
||||
for (call_op.args, 0..) |arg_ref, j| {
|
||||
args[j + 1] = self.resolveRef(arg_ref);
|
||||
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)
|
||||
// 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);
|
||||
param_tys[0] = self.cached_ptr; // env
|
||||
if (has_ctx) param_tys[0] = self.cached_ptr; // __sx_ctx
|
||||
param_tys[ctx_slots] = self.cached_ptr; // env
|
||||
if (closure_params) |cp| {
|
||||
// Use declared closure param types and coerce args to match
|
||||
// cp contains user-visible params only (no env)
|
||||
for (0..call_op.args.len) |j| {
|
||||
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[j + 1] = llvm_pty;
|
||||
args[j + 1] = self.coerceArg(args[j + 1], llvm_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[j + 1] = c.LLVMTypeOf(args[j + 1]);
|
||||
param_tys[ctx_slots + 1 + j] = c.LLVMTypeOf(args[ctx_slots + 1 + j]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (args[1..], 0..) |arg, j| {
|
||||
param_tys[j + 1] = c.LLVMTypeOf(arg);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user