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).
42 lines
1.7 KiB
Plaintext
42 lines
1.7 KiB
Plaintext
// Phase 1 step 1.6 (PLAN-FFI.md): non-void return shapes through
|
|
// `#objc_call`. Each return type triggers a distinct LLVMBuildCall2
|
|
// function-type combination so emit_llvm's per-call-site lowering
|
|
// has to pick the right ABI per call.
|
|
//
|
|
// We exercise both nil-recv (libobjc guarantees zero result for
|
|
// every shape) and real-recv paths so the ABI is verified beyond
|
|
// "the runtime no-oped the call."
|
|
|
|
#import "modules/std.sx";
|
|
#import "modules/compiler.sx";
|
|
#import "modules/std/objc.sx";
|
|
|
|
main :: () -> s32 {
|
|
inline if OS == .macos {
|
|
// ── Nil-recv quick smoke ───────────────────────────────────
|
|
nil_cls := #objc_call(*void)(null, "class");
|
|
print("nil class = {}\n", nil_cls == null);
|
|
|
|
nil_n := #objc_call(s64)(null, "hash");
|
|
print("nil hash = {}\n", nil_n);
|
|
|
|
// ── Real-recv: NSObject ────────────────────────────────────
|
|
// *void return: [NSObject class] -> NSObject's metaclass (non-null,
|
|
// and conveniently == self when sent to the class itself).
|
|
ns_object := objc_getClass("NSObject".ptr);
|
|
meta := #objc_call(*void)(ns_object, "class");
|
|
print("meta non-null = {}\n", meta != null);
|
|
|
|
// s64 return: [obj hash] returns NSUInteger. On the NSObject
|
|
// class itself the value is implementation-defined but stable
|
|
// within a process — pinning it as non-zero is enough for ABI
|
|
// verification.
|
|
h := #objc_call(s64)(ns_object, "hash");
|
|
print("hash non-zero = {}\n", h != 0);
|
|
}
|
|
inline if OS != .macos {
|
|
print("skipped (not macos)\n");
|
|
}
|
|
0;
|
|
}
|