Skip to main content

mtl_sys/
msg_send.rs

1//! Architecture-aware Objective-C message sending.
2//!
3//! This module provides low-level message sending functions that handle
4//! the different calling conventions required by the Objective-C runtime
5//! on different architectures:
6//!
7//! - **x86_64**: Uses `objc_msgSend_fpret` for floating-point returns,
8//!   `objc_msgSend_stret` for structs > 16 bytes
9//! - **x86 (i386)**: Uses `objc_msgSend_fpret` for floating-point returns,
10//!   `objc_msgSend_stret` for structs > 8 bytes
11//! - **ARM64**: Always uses standard `objc_msgSend` (no special variants needed)
12//! - **ARM32**: Uses `objc_msgSend_stret` for class types > 4 bytes
13//!
14//! # C++ Equivalent
15//!
16//! From metal-cpp Foundation/NSObject.hpp:
17//! ```cpp
18//! template <typename _Ret, typename... _Args>
19//! _NS_INLINE _Ret NS::Object::sendMessage(const void* pObj, SEL selector, _Args... args)
20//! ```
21
22use std::ffi::c_void;
23
24use crate::runtime::Sel;
25
26// Link against libobjc for message sending functions
27#[link(name = "objc")]
28unsafe extern "C" {
29    fn objc_msgSend();
30
31    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
32    fn objc_msgSend_fpret();
33
34    #[cfg(not(target_arch = "aarch64"))]
35    fn objc_msgSend_stret();
36}
37
38/// Struct return threshold by architecture.
39///
40/// Structs larger than this threshold require `objc_msgSend_stret`.
41/// This matches the C++ code in NSObject.hpp:
42/// ```cpp
43/// constexpr size_t kStructLimit = (sizeof(std::uintptr_t) << 1);
44/// ```
45#[cfg(target_arch = "x86_64")]
46const STRET_THRESHOLD: usize = 16;
47
48#[cfg(target_arch = "x86")]
49const STRET_THRESHOLD: usize = 8;
50
51#[cfg(target_arch = "aarch64")]
52#[allow(dead_code)]
53const STRET_THRESHOLD: usize = usize::MAX; // Never use stret on ARM64
54
55#[cfg(target_arch = "arm")]
56const STRET_THRESHOLD: usize = 4;
57
58/// Check if a type requires the stret calling convention.
59#[inline]
60#[allow(dead_code)]
61#[allow(clippy::absurd_extreme_comparisons)]
62const fn requires_stret<T>() -> bool {
63    std::mem::size_of::<T>() > STRET_THRESHOLD
64}
65
66/// Check if a type is a floating-point type (for fpret on x86).
67#[inline]
68#[allow(dead_code)]
69fn is_float<T: 'static>() -> bool {
70    use std::any::TypeId;
71    TypeId::of::<T>() == TypeId::of::<f32>() || TypeId::of::<T>() == TypeId::of::<f64>()
72}
73
74// =============================================================================
75// Message send with 0 arguments
76// =============================================================================
77
78/// Send a message with no arguments.
79///
80/// # Safety
81///
82/// - `obj` must be a valid Objective-C object pointer or class pointer
83/// - `sel` must be a valid selector for a method that takes no arguments
84/// - The return type `R` must match the actual return type of the method
85#[inline]
86pub unsafe fn msg_send_0<R>(obj: *const c_void, sel: Sel) -> R {
87    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
88    if is_float::<R>() {
89        let f: unsafe extern "C" fn(*const c_void, Sel) -> R =
90            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
91        return unsafe { f(obj, sel) };
92    }
93
94    #[cfg(not(target_arch = "aarch64"))]
95    if requires_stret::<R>() {
96        let mut result = std::mem::MaybeUninit::<R>::uninit();
97        let f: unsafe extern "C" fn(*mut R, *const c_void, Sel) =
98            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
99        unsafe { f(result.as_mut_ptr(), obj, sel) };
100        return unsafe { result.assume_init() };
101    }
102
103    let f: unsafe extern "C" fn(*const c_void, Sel) -> R =
104        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
105    unsafe { f(obj, sel) }
106}
107
108// =============================================================================
109// Message send with 1 argument
110// =============================================================================
111
112/// Send a message with 1 argument.
113///
114/// # Safety
115///
116/// - `obj` must be a valid Objective-C object pointer or class pointer
117/// - `sel` must be a valid selector for a method that takes 1 argument
118/// - The argument type `A` must match the expected parameter type
119/// - The return type `R` must match the actual return type of the method
120#[inline]
121pub unsafe fn msg_send_1<R, A>(obj: *const c_void, sel: Sel, a: A) -> R {
122    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
123    if is_float::<R>() {
124        let f: unsafe extern "C" fn(*const c_void, Sel, A) -> R =
125            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
126        return unsafe { f(obj, sel, a) };
127    }
128
129    #[cfg(not(target_arch = "aarch64"))]
130    if requires_stret::<R>() {
131        let mut result = std::mem::MaybeUninit::<R>::uninit();
132        let f: unsafe extern "C" fn(*mut R, *const c_void, Sel, A) =
133            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
134        unsafe { f(result.as_mut_ptr(), obj, sel, a) };
135        return unsafe { result.assume_init() };
136    }
137
138    let f: unsafe extern "C" fn(*const c_void, Sel, A) -> R =
139        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
140    unsafe { f(obj, sel, a) }
141}
142
143// =============================================================================
144// Message send with 2 arguments
145// =============================================================================
146
147/// Send a message with 2 arguments.
148///
149/// # Safety
150///
151/// See `msg_send_0` for safety requirements.
152#[inline]
153pub unsafe fn msg_send_2<R, A, B>(obj: *const c_void, sel: Sel, a: A, b: B) -> R {
154    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
155    if is_float::<R>() {
156        let f: unsafe extern "C" fn(*const c_void, Sel, A, B) -> R =
157            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
158        return unsafe { f(obj, sel, a, b) };
159    }
160
161    #[cfg(not(target_arch = "aarch64"))]
162    if requires_stret::<R>() {
163        let mut result = std::mem::MaybeUninit::<R>::uninit();
164        let f: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B) =
165            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
166        unsafe { f(result.as_mut_ptr(), obj, sel, a, b) };
167        return unsafe { result.assume_init() };
168    }
169
170    let f: unsafe extern "C" fn(*const c_void, Sel, A, B) -> R =
171        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
172    unsafe { f(obj, sel, a, b) }
173}
174
175// =============================================================================
176// Message send with 3 arguments
177// =============================================================================
178
179/// Send a message with 3 arguments.
180///
181/// # Safety
182///
183/// See `msg_send_0` for safety requirements.
184#[inline]
185pub unsafe fn msg_send_3<R, A, B, C>(obj: *const c_void, sel: Sel, a: A, b: B, c: C) -> R {
186    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
187    if is_float::<R>() {
188        let f: unsafe extern "C" fn(*const c_void, Sel, A, B, C) -> R =
189            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
190        return unsafe { f(obj, sel, a, b, c) };
191    }
192
193    #[cfg(not(target_arch = "aarch64"))]
194    if requires_stret::<R>() {
195        let mut result = std::mem::MaybeUninit::<R>::uninit();
196        let f: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C) =
197            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
198        unsafe { f(result.as_mut_ptr(), obj, sel, a, b, c) };
199        return unsafe { result.assume_init() };
200    }
201
202    let f: unsafe extern "C" fn(*const c_void, Sel, A, B, C) -> R =
203        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
204    unsafe { f(obj, sel, a, b, c) }
205}
206
207// =============================================================================
208// Message send with 4 arguments
209// =============================================================================
210
211/// Send a message with 4 arguments.
212///
213/// # Safety
214///
215/// See `msg_send_0` for safety requirements.
216#[inline]
217pub unsafe fn msg_send_4<R, A, B, C, D>(obj: *const c_void, sel: Sel, a: A, b: B, c: C, d: D) -> R {
218    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
219    if is_float::<R>() {
220        let f: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D) -> R =
221            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
222        return unsafe { f(obj, sel, a, b, c, d) };
223    }
224
225    #[cfg(not(target_arch = "aarch64"))]
226    if requires_stret::<R>() {
227        let mut result = std::mem::MaybeUninit::<R>::uninit();
228        let f: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D) =
229            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
230        unsafe { f(result.as_mut_ptr(), obj, sel, a, b, c, d) };
231        return unsafe { result.assume_init() };
232    }
233
234    let f: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D) -> R =
235        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
236    unsafe { f(obj, sel, a, b, c, d) }
237}
238
239// =============================================================================
240// Message send with 5 arguments
241// =============================================================================
242
243/// Send a message with 5 arguments.
244///
245/// # Safety
246///
247/// See `msg_send_0` for safety requirements.
248#[inline]
249pub unsafe fn msg_send_5<R, A, B, C, D, E>(
250    obj: *const c_void,
251    sel: Sel,
252    a: A,
253    b: B,
254    c: C,
255    d: D,
256    e: E,
257) -> R {
258    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
259    if is_float::<R>() {
260        let f: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E) -> R =
261            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
262        return unsafe { f(obj, sel, a, b, c, d, e) };
263    }
264
265    #[cfg(not(target_arch = "aarch64"))]
266    if requires_stret::<R>() {
267        let mut result = std::mem::MaybeUninit::<R>::uninit();
268        let f: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D, E) =
269            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
270        unsafe { f(result.as_mut_ptr(), obj, sel, a, b, c, d, e) };
271        return unsafe { result.assume_init() };
272    }
273
274    let f: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E) -> R =
275        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
276    unsafe { f(obj, sel, a, b, c, d, e) }
277}
278
279// =============================================================================
280// Message send with 6 arguments
281// =============================================================================
282
283/// Send a message with 6 arguments.
284///
285/// # Safety
286///
287/// See `msg_send_0` for safety requirements.
288#[inline]
289#[allow(clippy::too_many_arguments)]
290pub unsafe fn msg_send_6<R, A, B, C, D, E, F>(
291    obj: *const c_void,
292    sel: Sel,
293    a: A,
294    b: B,
295    c: C,
296    d: D,
297    e: E,
298    f_arg: F,
299) -> R {
300    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
301    if is_float::<R>() {
302        let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F) -> R =
303            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
304        return unsafe { func(obj, sel, a, b, c, d, e, f_arg) };
305    }
306
307    #[cfg(not(target_arch = "aarch64"))]
308    if requires_stret::<R>() {
309        let mut result = std::mem::MaybeUninit::<R>::uninit();
310        let func: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D, E, F) =
311            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
312        unsafe { func(result.as_mut_ptr(), obj, sel, a, b, c, d, e, f_arg) };
313        return unsafe { result.assume_init() };
314    }
315
316    let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F) -> R =
317        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
318    unsafe { func(obj, sel, a, b, c, d, e, f_arg) }
319}
320
321// =============================================================================
322// Message send with 7 arguments
323// =============================================================================
324
325/// Send a message with 7 arguments.
326///
327/// # Safety
328///
329/// See `msg_send_0` for safety requirements.
330#[inline]
331#[allow(clippy::too_many_arguments)]
332pub unsafe fn msg_send_7<R, A, B, C, D, E, F, G>(
333    obj: *const c_void,
334    sel: Sel,
335    a: A,
336    b: B,
337    c: C,
338    d: D,
339    e: E,
340    f_arg: F,
341    g: G,
342) -> R {
343    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
344    if is_float::<R>() {
345        let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F, G) -> R =
346            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
347        return unsafe { func(obj, sel, a, b, c, d, e, f_arg, g) };
348    }
349
350    #[cfg(not(target_arch = "aarch64"))]
351    if requires_stret::<R>() {
352        let mut result = std::mem::MaybeUninit::<R>::uninit();
353        let func: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D, E, F, G) =
354            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
355        unsafe { func(result.as_mut_ptr(), obj, sel, a, b, c, d, e, f_arg, g) };
356        return unsafe { result.assume_init() };
357    }
358
359    let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F, G) -> R =
360        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
361    unsafe { func(obj, sel, a, b, c, d, e, f_arg, g) }
362}
363
364// =============================================================================
365// Message send with 8 arguments
366// =============================================================================
367
368/// Send a message with 8 arguments.
369///
370/// # Safety
371///
372/// See `msg_send_0` for safety requirements.
373#[inline]
374#[allow(clippy::too_many_arguments)]
375pub unsafe fn msg_send_8<R, A, B, C, D, E, F, G, H>(
376    obj: *const c_void,
377    sel: Sel,
378    a: A,
379    b: B,
380    c: C,
381    d: D,
382    e: E,
383    f_arg: F,
384    g: G,
385    h: H,
386) -> R {
387    #[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
388    if is_float::<R>() {
389        let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F, G, H) -> R =
390            unsafe { std::mem::transmute(objc_msgSend_fpret as *const c_void) };
391        return unsafe { func(obj, sel, a, b, c, d, e, f_arg, g, h) };
392    }
393
394    #[cfg(not(target_arch = "aarch64"))]
395    if requires_stret::<R>() {
396        let mut result = std::mem::MaybeUninit::<R>::uninit();
397        let func: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D, E, F, G, H) =
398            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
399        unsafe { func(result.as_mut_ptr(), obj, sel, a, b, c, d, e, f_arg, g, h) };
400        return unsafe { result.assume_init() };
401    }
402
403    let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F, G, H) -> R =
404        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
405    unsafe { func(obj, sel, a, b, c, d, e, f_arg, g, h) }
406}
407
408// =============================================================================
409// Message send with 9 arguments (for texture loading and similar)
410// =============================================================================
411
412/// Send a message with 9 arguments.
413///
414/// # Safety
415///
416/// See `msg_send_0` for safety requirements.
417#[inline]
418#[allow(clippy::too_many_arguments)]
419pub unsafe fn msg_send_9<R, A, B, C, D, E, F, G, H, I>(
420    obj: *const c_void,
421    sel: Sel,
422    a: A,
423    b: B,
424    c: C,
425    d: D,
426    e: E,
427    f_arg: F,
428    g: G,
429    h: H,
430    i: I,
431) -> R {
432    // For 9+ args, we don't bother with fpret optimization (rare case)
433    #[cfg(not(target_arch = "aarch64"))]
434    if requires_stret::<R>() {
435        let mut result = std::mem::MaybeUninit::<R>::uninit();
436        let func: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D, E, F, G, H, I) =
437            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
438        unsafe { func(result.as_mut_ptr(), obj, sel, a, b, c, d, e, f_arg, g, h, i) };
439        return unsafe { result.assume_init() };
440    }
441
442    let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F, G, H, I) -> R =
443        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
444    unsafe { func(obj, sel, a, b, c, d, e, f_arg, g, h, i) }
445}
446
447// =============================================================================
448// Message send with 10 arguments (for IO command buffer texture loading)
449// =============================================================================
450
451/// Send a message with 10 arguments.
452///
453/// # Safety
454///
455/// See `msg_send_0` for safety requirements.
456#[inline]
457#[allow(clippy::too_many_arguments)]
458pub unsafe fn msg_send_10<R, A, B, C, D, E, F, G, H, I, J>(
459    obj: *const c_void,
460    sel: Sel,
461    a: A,
462    b: B,
463    c: C,
464    d: D,
465    e: E,
466    f_arg: F,
467    g: G,
468    h: H,
469    i: I,
470    j: J,
471) -> R {
472    #[cfg(not(target_arch = "aarch64"))]
473    if requires_stret::<R>() {
474        let mut result = std::mem::MaybeUninit::<R>::uninit();
475        let func: unsafe extern "C" fn(*mut R, *const c_void, Sel, A, B, C, D, E, F, G, H, I, J) =
476            unsafe { std::mem::transmute(objc_msgSend_stret as *const c_void) };
477        unsafe {
478            func(
479                result.as_mut_ptr(),
480                obj,
481                sel,
482                a,
483                b,
484                c,
485                d,
486                e,
487                f_arg,
488                g,
489                h,
490                i,
491                j,
492            )
493        };
494        return unsafe { result.assume_init() };
495    }
496
497    let func: unsafe extern "C" fn(*const c_void, Sel, A, B, C, D, E, F, G, H, I, J) -> R =
498        unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
499    unsafe { func(obj, sel, a, b, c, d, e, f_arg, g, h, i, j) }
500}