makepad/libs/relax/compiler/tests/fnptr_c_abi.rs
Admin b22a714840 makepad run: the Rust JIT (libs/relax) and makepad-remote
The JIT that runs makepad apps from source: lexer, front end, type checker,
lazy borrow checker (Gandalf; `--check-first` opts in), codegen, session and
warm/cold caches keyed by compiler stamp, provided crates bound late,
startup images, live patching. User-facing name is `makepad run` (binary
`makepad`, MAKEPAD_RUN_* env, ~/.makepad-run); libs/relax stays the internal
library name. `makepad check` reports rustc-style diagnostics with rustc
error codes and checks a whole workspace (`--workspace`). Runs on macOS,
Linux, Windows and Android. makepad-remote: one tool schema for apps and the
WM (app/1): grabs, captures, traces, frame profiles and tweaks.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 12:26:21 +02:00

134 lines
3.9 KiB
Rust

// Calls through `extern "C" fn` pointers use the C calling convention (the pointer type's ABI is
// the call's ABI; boot lane B1a, the objc_msgSend shape in platform/src/os/apple/metal.rs):
// C functions taken as pointers with HFA / packed / > 16-byte aggregate arguments and results,
// a dlsym'ed symbol transmuted to its signature, Rust `extern "C"` fns called through pointers,
// and C-ABI pointers used through the Fn traits (generic and `dyn`). rustc: all pass.
extern "C" {
fn cabs(z: Cplx) -> f64;
fn cexp(z: Cplx) -> Cplx;
fn div(a: i32, b: i32) -> DivT;
fn ldiv(a: i64, b: i64) -> LDivT;
fn dlsym(handle: *mut u8, name: *const u8) -> *mut u8;
}
#[repr(C)]
#[derive(Clone, Copy)]
struct Cplx {
re: f64,
im: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct DivT {
quot: i32,
rem: i32,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct LDivT {
quot: i64,
rem: i64,
}
/// > 16 bytes: by reference in C on both arm64 and x64, returned through a result slot
#[repr(C)]
#[derive(Clone, Copy)]
struct Region {
origin: [u64; 3],
size: [u64; 3],
}
extern "C" fn region_sum(r: Region, k: u64) -> u64 {
r.origin[0] + r.origin[1] + r.origin[2] + r.size[0] + r.size[1] + r.size[2] + k
}
extern "C" fn region_grow(r: Region, d: f64, z: Cplx) -> Region {
let mut o = r;
for i in 0..3 {
o.size[i] += d as u64 + z.re as u64;
}
o
}
extern "C" fn narrow(x: u32) -> u8 {
(x & 0xff) as u8
}
fn region() -> Region {
Region { origin: [1, 2, 3], size: [10, 20, 30] }
}
#[test]
fn c_functions_through_pointers() {
let fa: unsafe extern "C" fn(Cplx) -> f64 = cabs;
let fe: unsafe extern "C" fn(Cplx) -> Cplx = cexp;
let fd: unsafe extern "C" fn(i32, i32) -> DivT = div;
let fl: unsafe extern "C" fn(i64, i64) -> LDivT = ldiv;
unsafe {
assert!(fa(Cplx { re: 3.0, im: 4.0 }) == 5.0);
let e = fe(Cplx { re: 0.0, im: 0.0 });
assert!(e.re == 1.0 && e.im == 0.0);
let d = fd(17, 5);
assert!(d.quot == 3 && d.rem == 2);
let l = fl(-17, 5);
assert!(l.quot == -3 && l.rem == -2);
}
}
#[test]
fn transmuted_symbol() {
#[cfg(target_os = "macos")]
let h = -2isize as *mut u8;
#[cfg(not(target_os = "macos"))]
let h = 0isize as *mut u8;
let p = unsafe { dlsym(h, b"ldiv\0".as_ptr()) };
assert!(!p.is_null());
let f: extern "C" fn(i64, i64) -> LDivT = unsafe { std::mem::transmute(p) };
let l = f(100, 7);
assert!(l.quot == 14 && l.rem == 2);
}
#[test]
fn rust_c_fns_through_pointers() {
let s: extern "C" fn(Region, u64) -> u64 = region_sum;
let g: extern "C" fn(Region, f64, Cplx) -> Region = region_grow;
let n: extern "C" fn(u32) -> u8 = narrow;
assert!(s(region(), 4) == 70);
let r = g(region(), 1.0, Cplx { re: 2.0, im: 0.0 });
assert!(r.size == [13, 23, 33] && r.origin == [1, 2, 3]);
assert!(n(0x1234) == 0x34);
let table: [extern "C" fn(Region, u64) -> u64; 2] = [region_sum, region_sum];
assert!(table[1](region(), 0) == 66);
}
fn call_generic<F: Fn(Region, u64) -> u64>(f: F) -> u64 {
f(region(), 1)
}
struct Holder<T> {
f: extern "C" fn(T, u64) -> u64,
}
impl<T: Copy> Holder<T> {
fn call(&self, t: T) -> u64 {
(self.f)(t, 5)
}
}
// only Rust-ABI fn pointers implement the Fn traits (rustc_type_ir is_fn_trait_compatible):
// C-ABI pointers reach generic code wrapped in closures or as plain fields
#[test]
fn c_pointers_in_generic_code() {
let s: extern "C" fn(Region, u64) -> u64 = region_sum;
assert!(call_generic(move |r, k| s(r, k)) == 67);
let g: extern "C" fn(Region, f64, Cplx) -> Region = region_grow;
let d: Box<dyn Fn(Region) -> Region> = Box::new(move |r| g(r, 2.0, Cplx { re: 0.0, im: 0.0 }));
assert!(d(region()).size == [12, 22, 32]);
let h = Holder { f: region_sum };
assert!(h.call(region()) == 71);
let o: Option<extern "C" fn(u32) -> u8> = Some(narrow);
assert!(o.map_or(0, |f| f(0x2ff)) == 0xff);
}