ffi 1.6: objc_msg_send IR opcode + per-call-site LLVM fn type

102/102 regression tests pass; chess Android + iOS-sim still build
clean. `ffi-objc-call-04-primitive-returns` flips from xfail to
passing with both nil-recv and real-recv flavors of *void / s64
returns exercised.

Key change: a new `objc_msg_send` IR opcode bundles (recv, sel,
extra args) and carries the return type via the `Inst.ty` field.
emit_llvm.zig builds a per-call-site LLVM function type from the
argument Refs' IR types (recv/sel as ptr; extra args through
abiCoerceParamType) and dispatches with LLVMBuildCall2. One
declared `@objc_msgSend` symbol is reused across every return
type — opaque pointers make the function value type-erased, so
each call site picks its own ABI.

  before:  one (recv, sel) -> ptr LLVM declaration, hard-coded
           per call site; only void return wired in 1.3.
  after:   same declaration, each call site provides a fresh
           LLVMBuildCall2 fn-type → s64 / *void / bool / f64
           returns all dispatch correctly without separate FuncIds.

Selector init mechanism: stayed with the @llvm.global_ctors
constructor. Investigated clang's
`__DATA,__objc_selrefs` + `externally_initialized` shape — works
for fully-linked binaries (dyld substitutes the SEL at load
time) but **LLVM ORC JIT** (the engine behind `sx run`) doesn't
process Mach-O Obj-C metadata sections, so the slot keeps its
initial value (the method-name string pointer) and dispatch
crashes with "<null selector>". The portable choice: keep the
constructor AND inject a direct call to it at `main`'s entry —
idempotent under dyld (sel_registerName returns the same SEL on
re-registration), required for ORC JIT.

Files touched:
  src/ir/inst.zig    | new ObjcMsgSend struct + opcode
  src/ir/lower.zig   | drop the void-only restriction; emit the
                       new opcode; remove the orphaned
                       getObjcMsgSendFid path (objc_msgSend
                       declaration moved to emit_llvm)
  src/ir/emit_llvm.zig | objc_msg_send arm (per-call-site
                       LLVMBuildCall2); lazy `@objc_msgSend`
                       declaration via getObjcMsgSendValue;
                       emitObjcSelectorInit refactored to inject
                       the ctor call at main's entry
  src/ir/{print,interp}.zig | switch arms for the new opcode

`ffi-objc-call-03-selector-sharing.ir` snapshot updates to
reflect the new shape (the `call ... @objc_msgSend` call sites
no longer mention a typed wrapper).
This commit is contained in:
agra
2026-05-19 18:39:10 +03:00
parent baeab179c3
commit d43385112c
9 changed files with 221 additions and 110 deletions

View File

@@ -316,6 +316,11 @@ fn printInst(instruction: *const Inst, ref_idx: u32, tt: *const TypeTable, write
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
.objc_msg_send => |c| {
try writer.print("objc_msg_send recv=%{d} sel=%{d}(", .{ c.recv.index(), c.sel.index() });
try writeArgs(c.args, writer);
try writer.writeAll(") : ");
},
.compiler_call => |cc| {
const name = tt.getString(@enumFromInt(cc.name));
try writer.print("compiler_call \"{s}\"(", .{name});