Skip to main content

silicera_runtime/
ffi.rs

1//! Experimental C ABI surface for embedding Silicera-runtime in C/C++ hosts.
2//!
3//! Enabled with feature `c-abi`. All `unsafe` is isolated here. Rust callers
4//! should prefer the safe `Dispatcher` API.
5//!
6//! # Symbols
7//!
8//! | Symbol | Role |
9//! |--------|------|
10//! | `silicera_init` | Process/library init (idempotent) |
11//! | `silicera_profile_load` | Load `.hnep` into an opaque handle |
12//! | `silicera_variant_select` | Size → variant name (NUL-terminated into caller buffer) |
13//! | `silicera_profile_free` | Release handle |
14//!
15//! Status codes: `0` ok, negative = error (`-1` null, `-2` I/O/parse, `-3` buffer).
16
17use std::ffi::{CStr, CString};
18use std::os::raw::{c_char, c_int, c_ulonglong};
19use std::path::Path;
20use std::ptr;
21use std::sync::atomic::{AtomicBool, Ordering};
22use std::sync::Mutex;
23
24use crate::{Dispatcher, MismatchPolicy};
25
26static INIT: AtomicBool = AtomicBool::new(false);
27
28struct ProfileHandle {
29    dispatcher: Dispatcher,
30}
31
32/// Opaque profile handle for C callers.
33pub struct SiliceraProfile {
34    inner: Mutex<ProfileHandle>,
35}
36
37/// Initialize the Silicera C ABI (idempotent). Returns `0`.
38#[no_mangle]
39pub extern "C" fn silicera_init() -> c_int {
40    INIT.store(true, Ordering::SeqCst);
41    0
42}
43
44/// Load an HNEP profile from a UTF-8 path.
45///
46/// On success writes a non-null `*out_profile` that must be freed with
47/// [`silicera_profile_free`]. `strict_machine != 0` uses [`MismatchPolicy::StrictMachine`].
48///
49/// # Safety
50///
51/// `path` must be a valid NUL-terminated UTF-8 C string (or null → `-1`).
52/// `out_profile` must be a valid writable pointer.
53#[no_mangle]
54pub unsafe extern "C" fn silicera_profile_load(
55    path: *const c_char,
56    strict_machine: c_int,
57    out_profile: *mut *mut SiliceraProfile,
58) -> c_int {
59    if path.is_null() || out_profile.is_null() {
60        return -1;
61    }
62    // SAFETY: caller guarantees NUL-terminated C string.
63    let cstr = unsafe { CStr::from_ptr(path) };
64    let path_str = match cstr.to_str() {
65        Ok(s) => s,
66        Err(_) => return -2,
67    };
68    let policy = if strict_machine != 0 {
69        MismatchPolicy::StrictMachine
70    } else {
71        MismatchPolicy::FallbackBaseline
72    };
73    let dispatcher = match Dispatcher::open(Path::new(path_str), policy) {
74        Ok(d) => d,
75        Err(_) => return -2,
76    };
77    let boxed = Box::new(SiliceraProfile {
78        inner: Mutex::new(ProfileHandle { dispatcher }),
79    });
80    // SAFETY: caller guarantees out_profile is writable.
81    unsafe {
82        *out_profile = Box::into_raw(boxed);
83    }
84    0
85}
86
87/// Select a variant name for `size_bytes` into `buf` (NUL-terminated).
88///
89/// Writes at most `buf_len` bytes including the trailing NUL.
90///
91/// # Safety
92///
93/// `profile` must be a handle from [`silicera_profile_load`] (or null → `-1`).
94/// `buf` must point to a writable buffer of `buf_len` bytes when `buf_len > 0`.
95#[no_mangle]
96pub unsafe extern "C" fn silicera_variant_select(
97    profile: *mut SiliceraProfile,
98    size_bytes: c_ulonglong,
99    buf: *mut c_char,
100    buf_len: usize,
101) -> c_int {
102    if profile.is_null() || buf.is_null() || buf_len == 0 {
103        return -1;
104    }
105    // SAFETY: handle from silicera_profile_load; not freed yet.
106    let handle = unsafe { &*profile };
107    let name = {
108        let g = match handle.inner.lock() {
109            Ok(g) => g,
110            Err(_) => return -2,
111        };
112        g.dispatcher.size(size_bytes).to_string()
113    };
114    let cstr = match CString::new(name) {
115        Ok(c) => c,
116        Err(_) => return -2,
117    };
118    let bytes = cstr.as_bytes_with_nul();
119    if bytes.len() > buf_len {
120        return -3;
121    }
122    // SAFETY: buf has buf_len writable bytes.
123    unsafe {
124        ptr::copy_nonoverlapping(bytes.as_ptr() as *const c_char, buf, bytes.len());
125    }
126    0
127}
128
129/// Free a profile handle from [`silicera_profile_load`].
130///
131/// # Safety
132///
133/// `profile` must be null or a unique handle from `silicera_profile_load`.
134/// Double-free is undefined.
135#[no_mangle]
136pub unsafe extern "C" fn silicera_profile_free(profile: *mut SiliceraProfile) {
137    if profile.is_null() {
138        return;
139    }
140    // SAFETY: unique ownership from Box::into_raw in load.
141    unsafe {
142        drop(Box::from_raw(profile));
143    }
144}