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:
@@ -1,28 +1,38 @@
|
||||
// Phase 1 step 1.6 (PLAN-FFI.md): non-void return shapes through
|
||||
// `#objc_call`. The Phase 1.3 lowering hardcoded void+2-arg only;
|
||||
// this test exercises five primitive return types via real
|
||||
// Foundation classes:
|
||||
// `#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.
|
||||
//
|
||||
// *void — [NSObject class] returns Class (ptr)
|
||||
// bool — [NSObject isMemberOfClass: cls] returns BOOL
|
||||
// s32 — [NSString length] returns NSUInteger (treated as s32 here)
|
||||
// s64 — same shape, wider
|
||||
// f64 — [NSNumber doubleValue] returns double
|
||||
//
|
||||
// Today (1.6a, xfail): the lowering rejects non-void returns with
|
||||
// a diagnostic. Snapshot pins the diagnostic.
|
||||
// Next (1.6b/c): emit_llvm builds a per-call-site LLVM function
|
||||
// type from (recv, sel, args, ret_ty), shares one declared
|
||||
// `@objc_msgSend` symbol, dispatches with the right ABI.
|
||||
// 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 {
|
||||
// *void return: [NSObject class] → Class
|
||||
cls := #objc_call(*void)(null, "class");
|
||||
print("class non-null = {}\n", cls != null);
|
||||
// ── 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");
|
||||
|
||||
Reference in New Issue
Block a user