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:
@@ -179,6 +179,15 @@ pub const Op = union(enum) {
|
||||
call_builtin: BuiltinCall,
|
||||
compiler_call: CompilerCall,
|
||||
|
||||
/// `#objc_call(ReturnT)(recv, sel, args...)` — dispatched through
|
||||
/// `objc_msgSend`. emit_llvm.zig synthesizes a per-call-site LLVM
|
||||
/// function type from the arg/result Refs and reuses a single
|
||||
/// declared `@objc_msgSend` symbol across all return-type
|
||||
/// variants. Encoded as its own opcode (instead of `.call` /
|
||||
/// `.call_indirect`) so the IR doesn't need a separate FuncId
|
||||
/// per signature shape.
|
||||
objc_msg_send: ObjcMsgSend,
|
||||
|
||||
// ── Protocol dispatch ───────────────────────────────────────────
|
||||
protocol_call_dynamic: ProtocolCall, // vtable/inline dispatch
|
||||
protocol_erase: ProtocolErase, // concrete → protocol value (xx)
|
||||
@@ -284,6 +293,17 @@ pub const CallIndirect = struct {
|
||||
args: []const Ref,
|
||||
};
|
||||
|
||||
/// `#objc_call` dispatch through `objc_msgSend`. emit_llvm reads
|
||||
/// `recv`/`sel`/each arg's IR type to build the per-call-site LLVM
|
||||
/// function type; the instruction's own `ty` field (`Inst.ty`) is the
|
||||
/// Obj-C return type. One declared `@objc_msgSend` symbol is shared
|
||||
/// across every distinct signature shape.
|
||||
pub const ObjcMsgSend = struct {
|
||||
recv: Ref,
|
||||
sel: Ref,
|
||||
args: []const Ref, // additional args after recv + sel
|
||||
};
|
||||
|
||||
pub const BuiltinCall = struct {
|
||||
builtin: BuiltinId,
|
||||
args: []const Ref,
|
||||
|
||||
Reference in New Issue
Block a user