Skip to main content

weaveffi_core/
cabi.rs

1//! Shared rendering of the **C ABI declarations** from a
2//! [`BindingModel`](crate::model::BindingModel).
3//!
4//! Both the C generator (which emits the canonical `{prefix}.h`) and the C++
5//! generator (whose idiomatic wrapper opens an `extern "C"` block re-declaring
6//! the same symbols) render their C declarations through this module. Before it
7//! existed the two re-derived the ABI independently and drifted. Most visibly,
8//! the C++ `extern "C"` block lowered `iter<T>` as a list and omitted callbacks
9//! and listeners entirely. Routing both through one model-driven renderer makes
10//! that class of drift impossible.
11
12use std::fmt::Write;
13
14use crate::abi::AbiParam;
15use crate::codegen::common::{emit_doc as common_emit_doc, DocCommentStyle};
16use crate::codegen::CodeWriter;
17use crate::model::{
18    AbiFn, CallShape, EnumBinding, ErrorBinding, FnBinding, InterfaceBinding, ModuleBinding,
19    StructBinding,
20};
21
22/// Emit a `/** ... */` doc comment at `indent`.
23pub fn emit_doc(out: &mut String, doc: &Option<String>, indent: &str) {
24    common_emit_doc(out, doc, indent, DocCommentStyle::Javadoc);
25}
26
27/// Join lowered ABI slots into a `"<c-type> <name>, ..."` declaration string.
28pub fn params_str(params: &[AbiParam], prefix: &str) -> String {
29    params
30        .iter()
31        .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name))
32        .collect::<Vec<_>>()
33        .join(", ")
34}
35
36/// The export-visibility macro name for `prefix`, for example `WEAVEFFI_API`.
37///
38/// Every exported function prototype is tagged with this macro so a non-Rust
39/// producer that implements the header can export the symbols under hidden
40/// default visibility, and Windows consumers import them through `dllimport`.
41/// See [`render_visibility_macros`] for the macro's definition.
42fn export_macro(prefix: &str) -> String {
43    format!("{}_API", prefix.to_uppercase())
44}
45
46/// The deprecation macro name for `prefix`, for example `WEAVEFFI_DEPRECATED`.
47///
48/// Used in place of a bare `__attribute__((deprecated))` so the marker also
49/// compiles under MSVC (which spells it `__declspec(deprecated(...))`).
50fn deprecated_macro(prefix: &str) -> String {
51    format!("{}_DEPRECATED", prefix.to_uppercase())
52}
53
54/// Render the portable export-visibility and deprecation macros that the C ABI
55/// declarations are tagged with.
56///
57/// The C ABI header is both consumed (callers link the prebuilt library) and,
58/// for non-Rust producers, implemented directly (C, C++, or Zig supply the
59/// symbols). A bare prototype exports nothing under hidden default visibility
60/// (`-fvisibility=hidden`, the norm for release builds and the MSVC default),
61/// so an implementing library compiled that way ships no usable symbols. These
62/// macros fix that portably:
63///
64/// - `{PREFIX}_API` expands to `__declspec(dllexport)` when the producer
65///   defines `{PREFIX}_BUILD`, `__declspec(dllimport)` otherwise on Windows,
66///   `__attribute__((used, visibility("default")))` under Emscripten,
67///   `__attribute__((visibility("default")))` on GCC and Clang, and nothing
68///   elsewhere. The Emscripten spelling matches `EMSCRIPTEN_KEEPALIVE`: the
69///   `used` attribute keeps every tagged symbol alive through Emscripten's
70///   aggressive dead-code elimination, so the exports survive without the
71///   producer enumerating them in `-sEXPORTED_FUNCTIONS`.
72/// - `{PREFIX}_DEPRECATED(msg)` expands to the compiler's deprecation marker.
73///
74/// Both definitions are wrapped in `#ifndef` guards so a translation unit that
75/// includes both the C header and the C++ header (which inlines the same
76/// declarations) defines each macro only once. The names are derived from the
77/// configured symbol prefix so two WeaveFFI libraries included together never
78/// collide.
79pub fn render_visibility_macros(out: &mut String, prefix: &str) {
80    let body = r#"#ifndef @U@_API
81#  if defined(_WIN32) || defined(__CYGWIN__)
82#    ifdef @U@_BUILD
83#      define @U@_API __declspec(dllexport)
84#    else
85#      define @U@_API __declspec(dllimport)
86#    endif
87#  elif defined(__EMSCRIPTEN__)
88#    define @U@_API __attribute__((used, visibility("default")))
89#  elif defined(__GNUC__) && (__GNUC__ >= 4)
90#    define @U@_API __attribute__((visibility("default")))
91#  else
92#    define @U@_API
93#  endif
94#endif
95
96#ifndef @U@_DEPRECATED
97#  if defined(_MSC_VER)
98#    define @U@_DEPRECATED(msg) __declspec(deprecated(msg))
99#  elif defined(__GNUC__) || defined(__clang__)
100#    define @U@_DEPRECATED(msg) __attribute__((deprecated(msg)))
101#  else
102#    define @U@_DEPRECATED(msg)
103#  endif
104#endif
105
106"#;
107    out.push_str(&body.replace("@U@", &prefix.to_uppercase()));
108}
109
110/// Render a full `{API} {ret} {symbol}({params});` declaration for a lowered
111/// symbol, tagged with the export-visibility macro (see
112/// [`render_visibility_macros`]).
113pub fn fn_decl(out: &mut String, f: &AbiFn, prefix: &str) {
114    let _ = writeln!(
115        out,
116        "{} {} {}({});",
117        export_macro(prefix),
118        f.ret.render_c(prefix),
119        f.symbol,
120        params_str(&f.params, prefix)
121    );
122}
123
124/// Render the runtime typedefs and helper prototypes (`handle_t`, `error`,
125/// `free_*`, `alloc`/`dealloc`, `cancel_token`) that every WeaveFFI C surface
126/// depends on.
127///
128/// `alloc`/`dealloc` back the Wasm JavaScript glue, which stages strings,
129/// bytes, and arrays into linear memory before each call. Native consumers
130/// never call them, but a producer targeting WebAssembly (for example a C
131/// library built with Emscripten) must export them; the generated
132/// `{prefix}.c` scaffold provides malloc/free-backed defaults.
133pub fn render_runtime_decls(out: &mut String, prefix: &str) {
134    let api = export_macro(prefix);
135    let _ = write!(
136        out,
137        "typedef uint64_t {prefix}_handle_t;\n\n\
138         typedef struct {prefix}_error {{ int32_t code; const char* message; }} {prefix}_error;\n\n\
139         {api} void {prefix}_error_clear({prefix}_error* err);\n\
140         {api} void {prefix}_free_string(const char* ptr);\n\
141         {api} void {prefix}_free_bytes(uint8_t* ptr, size_t len);\n\n\
142         /* Linear-memory allocator used by the Wasm JS glue to stage call\n   \
143           arguments. Native consumers never call these; producers targeting\n   \
144           WebAssembly must export them (the generated {prefix}.c provides\n   \
145           malloc/free-backed defaults). */\n\
146         {api} uint8_t* {prefix}_alloc(uint32_t size);\n\
147         {api} void {prefix}_dealloc(uint8_t* ptr, uint32_t size);\n\n\
148         typedef struct {prefix}_cancel_token {prefix}_cancel_token;\n\
149         {api} {prefix}_cancel_token* {prefix}_cancel_token_create(void);\n\
150         {api} void {prefix}_cancel_token_cancel({prefix}_cancel_token* token);\n\
151         {api} bool {prefix}_cancel_token_is_cancelled(const {prefix}_cancel_token* token);\n\
152         {api} void {prefix}_cancel_token_destroy({prefix}_cancel_token* token);\n\n",
153    );
154}
155
156/// Render an enum's discriminant constants as a C `typedef enum` named
157/// `type_name`. Multi-line when any variant is documented.
158fn render_enum_constants(out: &mut String, e: &EnumBinding, type_name: &str) {
159    let mut w = CodeWriter::four_space();
160    w.doc(&e.doc, DocCommentStyle::Javadoc);
161    if e.variants.iter().any(|v| v.doc.is_some()) {
162        w.block("typedef enum {", format!("}} {type_name};"), |w| {
163            let last = e.variants.len();
164            for (i, v) in e.variants.iter().enumerate() {
165                w.doc(&v.doc, DocCommentStyle::Javadoc);
166                let comma = if i + 1 == last { "" } else { "," };
167                w.line(format!("{} = {}{comma}", v.c_const, v.value));
168            }
169        });
170    } else {
171        let variants: Vec<String> = e
172            .variants
173            .iter()
174            .map(|v| format!("{} = {}", v.c_const, v.value))
175            .collect();
176        w.line(format!(
177            "typedef enum {{ {} }} {type_name};",
178            variants.join(", ")
179        ));
180    }
181    out.push_str(&w.finish());
182}
183
184/// Render a C-style enum typedef. Multi-line when any variant is documented.
185pub fn render_enum_decl(out: &mut String, e: &EnumBinding) {
186    render_enum_constants(out, e, &e.c_tag);
187}
188
189/// Render the *discriminant* enum of a rich (algebraic) enum, named
190/// `{c_tag}_Tag`. The payload-carrying value itself is an opaque struct
191/// `{c_tag}` (forward-declared via [`render_module_type_tags`]); the tag getter
192/// returns one of these discriminant constants as `int32_t`.
193fn render_rich_enum_tag_decl(out: &mut String, e: &EnumBinding) {
194    let tag_enum = format!("{}_Tag", e.c_tag);
195    render_enum_constants(out, e, &tag_enum);
196}
197
198/// Render the function surface of a rich (algebraic) enum: the tag getter, each
199/// variant's constructor and field getters, then the destructor. Assumes the
200/// opaque object tag and every referenced type tag are already forward-declared.
201fn render_rich_enum_fn_decls(out: &mut String, e: &EnumBinding, prefix: &str) {
202    let Some(rich) = &e.rich else {
203        return;
204    };
205    let api = export_macro(prefix);
206    let tag = &e.c_tag;
207    emit_doc(out, &e.doc, "");
208    let _ = writeln!(out, "{api} int32_t {}(const {tag}* self);", rich.tag_symbol);
209    for v in &rich.variants {
210        emit_doc(out, &v.doc, "");
211        fn_decl(out, &v.create, prefix);
212        for field in &v.fields {
213            emit_doc(out, &field.doc, "");
214            let mut parts = vec![format!("const {tag}* self")];
215            parts.extend(
216                field
217                    .getter_out_params
218                    .iter()
219                    .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
220            );
221            let _ = writeln!(
222                out,
223                "{api} {} {}({});",
224                field.getter_ret.render_c(prefix),
225                field.getter_symbol,
226                parts.join(", ")
227            );
228        }
229    }
230    let _ = writeln!(out, "{api} void {}({tag}* self);", rich.destroy_symbol);
231    out.push('\n');
232}
233
234/// Render the opaque struct/builder *tags* (forward typedefs) for one struct.
235///
236/// These reference no other types, so emitting every struct's tags before any
237/// function declaration lets a function in one module accept or return a struct
238/// declared in *another* module (a parent module referencing a child's type).
239fn render_struct_tags(out: &mut String, s: &StructBinding) {
240    let tag = &s.c_tag;
241    let _ = writeln!(out, "typedef struct {tag} {tag};");
242    if let Some(b) = &s.builder {
243        let bt = &b.builder_tag;
244        let _ = writeln!(out, "typedef struct {bt} {bt};");
245    }
246}
247
248/// Render the function declarations for one struct: create/destroy/getters and,
249/// if present, the fluent builder's new/setters/build/destroy. Assumes the
250/// struct (and every other struct it may reference) already has a forward
251/// typedef emitted via [`render_struct_tags`].
252fn render_struct_fn_decls(out: &mut String, s: &StructBinding, prefix: &str) {
253    let api = export_macro(prefix);
254    let tag = &s.c_tag;
255    emit_doc(out, &s.doc, "");
256    fn_decl(out, &s.create, prefix);
257    let _ = writeln!(out, "{api} void {}({tag}* ptr);", s.destroy_symbol);
258    for field in &s.fields {
259        emit_doc(out, &field.doc, "");
260        let mut parts = vec![format!("const {tag}* ptr")];
261        parts.extend(
262            field
263                .getter_out_params
264                .iter()
265                .map(|p| format!("{} {}", p.ty.render_c(prefix), p.name)),
266        );
267        let _ = writeln!(
268            out,
269            "{api} {} {}({});",
270            field.getter_ret.render_c(prefix),
271            field.getter_symbol,
272            parts.join(", ")
273        );
274    }
275    out.push('\n');
276
277    if let Some(b) = &s.builder {
278        let bt = &b.builder_tag;
279        let _ = writeln!(out, "{api} {bt}* {}(void);", b.new_symbol);
280        for (field, (_, setter)) in s.fields.iter().zip(&b.setters) {
281            emit_doc(out, &field.doc, "");
282            let _ = writeln!(
283                out,
284                "{api} void {setter}({bt}* builder, {});",
285                params_str(&field.value_params, prefix)
286            );
287        }
288        let _ = writeln!(
289            out,
290            "{api} {tag}* {}({bt}* builder, {prefix}_error* out_err);",
291            b.build_symbol
292        );
293        let _ = writeln!(out, "{api} void {}({bt}* builder);", b.destroy_symbol);
294        out.push('\n');
295    }
296}
297
298/// Render an error domain's code constants as a C `typedef enum` named by the
299/// domain's `c_tag`. These are the values a throwing function stores in
300/// `{prefix}_error.code`.
301fn render_error_domain_decl(out: &mut String, e: &ErrorBinding) {
302    let mut w = CodeWriter::four_space();
303    w.doc(
304        &Some(format!(
305            "Error codes reported by throwing functions in the `{}` module tree.",
306            e.owner_path.replace('_', ".")
307        )),
308        DocCommentStyle::Javadoc,
309    );
310    if e.codes.iter().any(|c| c.doc.is_some()) {
311        w.block("typedef enum {", format!("}} {};", e.c_tag), |w| {
312            let last = e.codes.len();
313            for (i, c) in e.codes.iter().enumerate() {
314                w.doc(&c.doc, DocCommentStyle::Javadoc);
315                let comma = if i + 1 == last { "" } else { "," };
316                w.line(format!("{} = {}{comma}", c.c_const, c.value));
317            }
318        });
319    } else {
320        let codes: Vec<String> = e
321            .codes
322            .iter()
323            .map(|c| format!("{} = {}", c.c_const, c.value))
324            .collect();
325        w.line(format!(
326            "typedef enum {{ {} }} {};",
327            codes.join(", "),
328            e.c_tag
329        ));
330    }
331    out.push_str(&w.finish());
332}
333
334/// Phase 1a: enum and error-code definitions for one module. These reference
335/// no other types, so they are emitted first across all modules.
336pub fn render_module_enum_defs(out: &mut String, module: &ModuleBinding) {
337    for e in &module.enums {
338        if e.is_rich() {
339            render_rich_enum_tag_decl(out, e);
340        } else {
341            render_enum_decl(out, e);
342        }
343    }
344    if let Some(err) = &module.error {
345        if err.declared_here {
346            render_error_domain_decl(out, err);
347        }
348    }
349}
350
351/// Phase 1b: opaque struct/builder/interface/iterator forward typedefs for one
352/// module. Pointers to these are all the C ABI ever uses, so a forward typedef
353/// is sufficient and lets declarations in any module reference any type.
354pub fn render_module_type_tags(out: &mut String, module: &ModuleBinding) {
355    // A rich (algebraic) enum is an opaque object, declared like a struct tag.
356    for e in &module.enums {
357        if e.is_rich() {
358            let t = &e.c_tag;
359            let _ = writeln!(out, "typedef struct {t} {t};");
360        }
361    }
362    for s in &module.structs {
363        render_struct_tags(out, s);
364    }
365    for i in &module.interfaces {
366        let t = &i.c_tag;
367        let _ = writeln!(out, "typedef struct {t} {t};");
368    }
369    for f in module.callables() {
370        if let CallShape::Iterator(it) = &f.shape {
371            let t = &it.iter_tag;
372            let _ = writeln!(out, "typedef struct {t} {t};");
373        }
374    }
375}
376
377/// Phase 1c: callback / async-callback function-pointer typedefs for one
378/// module. These may reference enums (by value) and structs (by pointer), so
379/// they are emitted after every module's enums and type tags.
380pub fn render_module_callback_types(out: &mut String, module: &ModuleBinding, prefix: &str) {
381    for cb in &module.callbacks {
382        emit_doc(out, &cb.doc, "");
383        let _ = writeln!(
384            out,
385            "typedef void (*{})({});",
386            cb.c_fn_type,
387            params_str(&cb.abi_params, prefix)
388        );
389    }
390    for f in module.callables() {
391        if let CallShape::Async(a) = &f.shape {
392            let _ = writeln!(
393                out,
394                "typedef void (*{})({});",
395                a.callback_type,
396                params_str(&a.callback_params, prefix)
397            );
398        }
399    }
400}
401
402/// Render one callable's prototypes according to its call shape (sync, async
403/// launcher, or iterator launch/next/destroy triple), with doc comment and
404/// deprecation marker.
405fn render_callable_decl(out: &mut String, f: &FnBinding, prefix: &str) {
406    let api = export_macro(prefix);
407    let deprecated = deprecated_macro(prefix);
408    emit_doc(out, &f.doc, "");
409    if let Some(msg) = &f.deprecated {
410        let _ = writeln!(out, "{deprecated}(\"{}\")", msg.replace('"', "\\\""));
411    }
412    match &f.shape {
413        CallShape::Iterator(it) => {
414            let t = &it.iter_tag;
415            fn_decl(out, &it.launch, prefix);
416            fn_decl(out, &it.next, prefix);
417            let _ = writeln!(out, "{api} void {}({t}* iter);", it.destroy_symbol);
418        }
419        CallShape::Async(a) => {
420            fn_decl(out, &a.launch, prefix);
421        }
422        CallShape::Sync(abi) => {
423            fn_decl(out, abi, prefix);
424        }
425    }
426}
427
428/// Render the function surface of one interface: constructors, statics,
429/// methods, then the destructor. Assumes the opaque tag is already
430/// forward-declared (phase 1b).
431fn render_interface_fn_decls(out: &mut String, i: &InterfaceBinding, prefix: &str) {
432    let api = export_macro(prefix);
433    let tag = &i.c_tag;
434    emit_doc(out, &i.doc, "");
435    for c in &i.constructors {
436        render_callable_decl(out, c, prefix);
437    }
438    for s in &i.statics {
439        render_callable_decl(out, s, prefix);
440    }
441    for m in &i.methods {
442        render_callable_decl(out, m, prefix);
443    }
444    let _ = writeln!(out, "{api} void {}({tag}* self);", i.destroy_symbol);
445    out.push('\n');
446}
447
448/// Phase 2: every function prototype for one module: struct create/destroy/
449/// getters and builders, interface members, listeners, then sync/async/
450/// iterator functions. All type tags and callback typedefs are assumed already
451/// emitted (phases 1a–1c). Caller controls the leading `// Module:` comment
452/// and any framing.
453pub fn render_module_fn_decls(out: &mut String, module: &ModuleBinding, prefix: &str) {
454    let api = export_macro(prefix);
455    for e in &module.enums {
456        render_rich_enum_fn_decls(out, e, prefix);
457    }
458    for s in &module.structs {
459        render_struct_fn_decls(out, s, prefix);
460    }
461    for i in &module.interfaces {
462        render_interface_fn_decls(out, i, prefix);
463    }
464    for l in &module.listeners {
465        emit_doc(out, &l.doc, "");
466        let _ = writeln!(
467            out,
468            "{api} uint64_t {}({} callback, void* context);",
469            l.register_symbol, l.callback_c_fn_type
470        );
471        emit_doc(out, &l.doc, "");
472        let _ = writeln!(out, "{api} void {}(uint64_t id);", l.unregister_symbol);
473    }
474    for f in &module.functions {
475        render_callable_decl(out, f, prefix);
476    }
477}
478
479/// Render the complete C ABI declaration surface for `modules` in
480/// dependency-safe order: all enum definitions, then all opaque type tags, then
481/// all callback typedefs, then per-module function prototypes. Emitting every
482/// type tag before any function lets a parent module's function reference a
483/// child module's struct: cross-module forward references the previous
484/// per-module interleaving could not express.
485///
486/// The runtime decls (`handle_t`, `error`, `free_*`, cancel token) are *not*
487/// emitted here; callers render those first (the C generator inserts its map
488/// convention comment in between).
489pub fn render_decls(
490    out: &mut String,
491    modules: &[ModuleBinding],
492    prefix: &str,
493    module_comments: bool,
494) {
495    for m in modules {
496        render_module_enum_defs(out, m);
497    }
498    for m in modules {
499        render_module_type_tags(out, m);
500    }
501    for m in modules {
502        render_module_callback_types(out, m, prefix);
503    }
504    out.push('\n');
505    for m in modules {
506        if module_comments {
507            let _ = writeln!(out, "// Module: {}", m.path);
508        }
509        render_module_fn_decls(out, m, prefix);
510        out.push('\n');
511    }
512}