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