Skip to main content

lmflow/ffi/
kernel.rs

1//! C ABI:算子注册与 Contract(端口类型契约)
2//!
3//! 由 [`super`](../index.html) 的分节拆出 —— 见 `ffi/mod.rs` 的模块头注释,
4//! 那里定义了整层的约定(catch_unwind 包裹、last_error、空指针检查、布局钉死)。
5#![allow(clippy::missing_safety_doc)]
6
7use std::ffi::{c_char, c_void};
8
9use crate::kernel::{KernelLanguage, KernelVTable};
10use crate::runtime::{self};
11use crate::status::Error;
12
13use super::*;
14
15// ---------------------------------------------------------------- 算子注册
16
17#[repr(C)]
18#[derive(Clone, Copy)]
19pub struct LMFlowKernelVTable {
20    pub create: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>,
21    pub get_contract: Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContract)>,
22    pub open: Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContext) -> i32>,
23    pub process: Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContext) -> i32>,
24    pub close: Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContext) -> i32>,
25    pub destroy: Option<unsafe extern "C" fn(*mut c_void)>,
26}
27
28#[no_mangle]
29pub unsafe extern "C" fn lmflow_register_kernel(
30    name: *const c_char,
31    vt: *const LMFlowKernelVTable,
32    factory: *mut c_void,
33) -> i32 {
34    register_kernel_impl(name, vt, factory, KernelLanguage::Unknown)
35}
36
37#[no_mangle]
38pub unsafe extern "C" fn lmflow_register_kernel_with_language(
39    name: *const c_char,
40    vt: *const LMFlowKernelVTable,
41    factory: *mut c_void,
42    language: u32,
43) -> i32 {
44    let language = match language {
45        0 => KernelLanguage::Unknown,
46        1 => KernelLanguage::Rust,
47        2 => KernelLanguage::Cpp,
48        3 => KernelLanguage::Python,
49        4 => KernelLanguage::C,
50        other => {
51            return guard(|| {
52                fail(Error::InvalidArg(format!(
53                    "unknown kernel implementation language id {other}"
54                )))
55            })
56        }
57    };
58    register_kernel_impl(name, vt, factory, language)
59}
60
61unsafe fn register_kernel_impl(
62    name: *const c_char,
63    vt: *const LMFlowKernelVTable,
64    factory: *mut c_void,
65    language: KernelLanguage,
66) -> i32 {
67    guard(|| {
68        let Some(n) = cstr(name) else {
69            return fail(Error::InvalidArg(
70                "kernel name is empty or not UTF-8".into(),
71            ));
72        };
73        if vt.is_null() {
74            return fail(Error::InvalidArg(format!("kernel `{n}` vtable is null")));
75        }
76        let v = &*vt;
77        // LMFlowKernelVTable 与 KernelVTable 布局相同,只是 ctx 参数的具体类型不同
78        // LMFlowKernelVTable 与 KernelVTable 的函数指针 ABI 完全相同,仅上下文参数的
79        // 具名类型不同(LMFlowContext*/LMFlowContract* ↔ void*)。显式标注转换目标类型。
80        type CtxFn = unsafe extern "C" fn(*mut c_void, *mut c_void) -> i32;
81        type ContractFn = unsafe extern "C" fn(*mut c_void, *mut c_void);
82        let converted = KernelVTable {
83            create: v.create,
84            get_contract: std::mem::transmute::<
85                Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContract)>,
86                Option<ContractFn>,
87            >(v.get_contract),
88            open: std::mem::transmute::<
89                Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContext) -> i32>,
90                Option<CtxFn>,
91            >(v.open),
92            process: std::mem::transmute::<
93                Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContext) -> i32>,
94                Option<CtxFn>,
95            >(v.process),
96            close: std::mem::transmute::<
97                Option<unsafe extern "C" fn(*mut c_void, *mut LMFlowContext) -> i32>,
98                Option<CtxFn>,
99            >(v.close),
100            destroy: v.destroy,
101        };
102        to_status(crate::kernel::register_with_language(
103            n, converted, factory, language,
104        ))
105    })
106}
107
108// ---------------------------------------------------------------- Contract
109
110#[no_mangle]
111pub unsafe extern "C" fn lmflow_contract_num_inputs(c: *const LMFlowContract) -> usize {
112    guard_val(0, || {
113        contract_mut(c as *mut LMFlowContract).map_or(0, |x| x.inputs.len())
114    })
115}
116#[no_mangle]
117pub unsafe extern "C" fn lmflow_contract_num_outputs(c: *const LMFlowContract) -> usize {
118    guard_val(0, || {
119        contract_mut(c as *mut LMFlowContract).map_or(0, |x| x.outputs.len())
120    })
121}
122#[no_mangle]
123pub unsafe extern "C" fn lmflow_contract_input_id(
124    c: *const LMFlowContract,
125    tag: *const c_char,
126    index: usize,
127) -> usize {
128    guard_val(INVALID_ID, || {
129        let Some(x) = contract_mut(c as *mut LMFlowContract) else {
130            return INVALID_ID;
131        };
132        x.inputs
133            .id_by_tag(cstr(tag).unwrap_or(""), index)
134            .unwrap_or(INVALID_ID)
135    })
136}
137#[no_mangle]
138pub unsafe extern "C" fn lmflow_contract_output_id(
139    c: *const LMFlowContract,
140    tag: *const c_char,
141    index: usize,
142) -> usize {
143    guard_val(INVALID_ID, || {
144        let Some(x) = contract_mut(c as *mut LMFlowContract) else {
145            return INVALID_ID;
146        };
147        x.outputs
148            .id_by_tag(cstr(tag).unwrap_or(""), index)
149            .unwrap_or(INVALID_ID)
150    })
151}
152#[no_mangle]
153pub unsafe extern "C" fn lmflow_contract_input_index(
154    c: *const LMFlowContract,
155    name: *const c_char,
156) -> usize {
157    guard_val(INVALID_ID, || {
158        let Some(x) = contract_mut(c as *mut LMFlowContract) else {
159            return INVALID_ID;
160        };
161        x.inputs
162            .index_by_name(cstr(name).unwrap_or(""))
163            .unwrap_or(INVALID_ID)
164    })
165}
166#[no_mangle]
167pub unsafe extern "C" fn lmflow_contract_output_index(
168    c: *const LMFlowContract,
169    name: *const c_char,
170) -> usize {
171    guard_val(INVALID_ID, || {
172        let Some(x) = contract_mut(c as *mut LMFlowContract) else {
173            return INVALID_ID;
174        };
175        x.outputs
176            .index_by_name(cstr(name).unwrap_or(""))
177            .unwrap_or(INVALID_ID)
178    })
179}
180
181/// contract 阶段的端口名指针需要一个稳定住处;用进程级 arena。
182fn contract_arena() -> &'static runtime::CStrArena {
183    static A: std::sync::LazyLock<runtime::CStrArena> =
184        std::sync::LazyLock::new(runtime::CStrArena::default);
185    &A
186}
187
188#[no_mangle]
189pub unsafe extern "C" fn lmflow_contract_input_name(
190    c: *const LMFlowContract,
191    idx: usize,
192) -> *const c_char {
193    guard_val(c"".as_ptr(), || {
194        contract_mut(c as *mut LMFlowContract)
195            .and_then(|x| x.inputs.name(idx).map(|s| contract_arena().intern(s)))
196            .unwrap_or(c"".as_ptr())
197    })
198}
199#[no_mangle]
200pub unsafe extern "C" fn lmflow_contract_output_name(
201    c: *const LMFlowContract,
202    idx: usize,
203) -> *const c_char {
204    guard_val(c"".as_ptr(), || {
205        contract_mut(c as *mut LMFlowContract)
206            .and_then(|x| x.outputs.name(idx).map(|s| contract_arena().intern(s)))
207            .unwrap_or(c"".as_ptr())
208    })
209}
210
211#[no_mangle]
212pub unsafe extern "C" fn lmflow_contract_input_set_any(c: *mut LMFlowContract, idx: usize) {
213    guard_val((), || {
214        if let Some(x) = contract_mut(c) {
215            if let Some(s) = x.input_types.get_mut(idx) {
216                *s = 0;
217            } else {
218                x.record_error(format!(
219                    "input port index {idx} is out of range (num_inputs={})",
220                    x.input_types.len()
221                ));
222            }
223        }
224    });
225}
226#[no_mangle]
227pub unsafe extern "C" fn lmflow_contract_output_set_any(c: *mut LMFlowContract, idx: usize) {
228    guard_val((), || {
229        if let Some(x) = contract_mut(c) {
230            if let Some(s) = x.output_types.get_mut(idx) {
231                *s = 0;
232            } else {
233                x.record_error(format!(
234                    "output port index {idx} is out of range (num_outputs={})",
235                    x.output_types.len()
236                ));
237            }
238        }
239    });
240}
241#[no_mangle]
242pub unsafe extern "C" fn lmflow_contract_input_set_type(
243    c: *mut LMFlowContract,
244    idx: usize,
245    type_id: u64,
246) {
247    guard_val((), || {
248        if let Some(x) = contract_mut(c) {
249            if let Some(s) = x.input_types.get_mut(idx) {
250                *s = type_id;
251            } else {
252                x.record_error(format!(
253                    "input port index {idx} is out of range (num_inputs={})",
254                    x.input_types.len()
255                ));
256            }
257        }
258    });
259}
260#[no_mangle]
261pub unsafe extern "C" fn lmflow_contract_output_set_type(
262    c: *mut LMFlowContract,
263    idx: usize,
264    type_id: u64,
265) {
266    guard_val((), || {
267        if let Some(x) = contract_mut(c) {
268            if let Some(s) = x.output_types.get_mut(idx) {
269                *s = type_id;
270            } else {
271                x.record_error(format!(
272                    "output port index {idx} is out of range (num_outputs={})",
273                    x.output_types.len()
274                ));
275            }
276        }
277    });
278}
279#[no_mangle]
280pub unsafe extern "C" fn lmflow_contract_require_side_packet(
281    c: *mut LMFlowContract,
282    name: *const c_char,
283) {
284    guard_val((), || {
285        if let Some(x) = contract_mut(c) {
286            if let Some(name) = cstr(name).filter(|name| !name.is_empty()) {
287                x.required_side_packets.push(name.to_string());
288            } else {
289                x.record_error("required side packet name must not be empty or invalid UTF-8");
290            }
291        }
292    });
293}
294
295#[no_mangle]
296pub unsafe extern "C" fn lmflow_contract_set_error(c: *mut LMFlowContract, message: *const c_char) {
297    guard_val((), || {
298        if let Some(x) = contract_mut(c) {
299            let message = cstr(message)
300                .filter(|message| !message.is_empty())
301                .unwrap_or("GetContract failed without a valid UTF-8 error message");
302            x.record_error(message);
303        }
304    });
305}