|
| 1 | +# Kore C ABI Design |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Add a minimal, stable C ABI for `kore` that allows non-Rust callers to create a kernel, step a caller-owned state buffer, read diagnostics, and destroy the kernel without needing to understand any Rust types or layouts. |
| 6 | + |
| 7 | +## Scope |
| 8 | + |
| 9 | +This phase adds a step-only ABI. It does not add state-initialization helpers, FFTW integration, HDF5, Python bindings, or README polish. The ABI surface is intentionally minimal so it can remain stable over time. |
| 10 | + |
| 11 | +## Public C Surface |
| 12 | + |
| 13 | +The public ABI consists of: |
| 14 | + |
| 15 | +- an opaque `KoreKernel` handle |
| 16 | +- plain-old-data structs for grid, config, diagnostics, and diagnostics summary |
| 17 | +- an integer-backed status enum typedef |
| 18 | +- lifecycle and execution functions only |
| 19 | +- a version query function |
| 20 | + |
| 21 | +State buffers remain caller-owned and are passed as raw contiguous `double _Complex*` pointers in row-major order. The ABI borrows those buffers only for the duration of each call and never allocates or frees caller memory. |
| 22 | + |
| 23 | +Recommended shape: |
| 24 | + |
| 25 | +```c |
| 26 | +typedef struct KoreKernel KoreKernel; |
| 27 | + |
| 28 | +typedef struct { |
| 29 | + uint64_t nx, ny, nz; |
| 30 | + double lx, ly, lz; |
| 31 | +} KoreGrid; |
| 32 | + |
| 33 | +typedef struct { |
| 34 | + double dt; |
| 35 | + double hbar_over_m; |
| 36 | + double poisson_scale; |
| 37 | + uint32_t threads; |
| 38 | +} KoreConfig; |
| 39 | + |
| 40 | +typedef struct { |
| 41 | + double mass; |
| 42 | + double max_density; |
| 43 | + double l2_norm; |
| 44 | +} KoreDiagnostics; |
| 45 | + |
| 46 | +typedef struct { |
| 47 | + double initial_mass; |
| 48 | + double final_mass; |
| 49 | + double min_mass; |
| 50 | + double max_mass; |
| 51 | + double max_mass_drift; |
| 52 | + uint64_t steps; |
| 53 | +} KoreDiagnosticsSummary; |
| 54 | + |
| 55 | +typedef enum { |
| 56 | + KORE_OK = 0, |
| 57 | + KORE_ERR_NULL = 1, |
| 58 | + KORE_ERR_INVALID = 2, |
| 59 | + KORE_ERR_INTERNAL = 3 |
| 60 | +} KoreStatus; |
| 61 | +``` |
| 62 | + |
| 63 | +The `KoreStatus` typedef should remain C89-friendly. The generated header must not expose Rust types such as `Complex64`, `Vec`, or `Result`. |
| 64 | + |
| 65 | +## Semantics And Ownership |
| 66 | + |
| 67 | +`kore_create` returns `NULL` on failure and can optionally write a status code to an out-pointer. That out-pointer is nullable and must be checked before writing. The same nullable-out-pointer rule applies to `kore_step_n` summary output and `kore_version` outputs. |
| 68 | + |
| 69 | +The `threads` field uses `0` to mean default runtime behavior. The current Rust implementation maps that to `None` and uses global Rayon defaults. |
| 70 | + |
| 71 | +`max_mass_drift` in `KoreDiagnosticsSummary` should be documented as relative drift, not absolute drift: |
| 72 | + |
| 73 | +`abs(mass_current - mass_initial) / mass_initial` |
| 74 | + |
| 75 | +The zero-initial-mass case should be handled explicitly. If the run remains identically zero, drift is `0.0`; otherwise `kore_step_n` should return an invalid-status error rather than emit a meaningless ratio. |
| 76 | + |
| 77 | +Recommended function set: |
| 78 | + |
| 79 | +```c |
| 80 | +KoreKernel* kore_create(KoreGrid grid, KoreConfig config, KoreStatus* out_status); |
| 81 | +void kore_destroy(KoreKernel* kernel); |
| 82 | + |
| 83 | +KoreStatus kore_step(KoreKernel* kernel, double _Complex* psi); |
| 84 | +KoreStatus kore_step_n( |
| 85 | + KoreKernel* kernel, |
| 86 | + double _Complex* psi, |
| 87 | + uint64_t steps, |
| 88 | + KoreDiagnosticsSummary* out_summary |
| 89 | +); |
| 90 | +KoreStatus kore_diagnostics( |
| 91 | + KoreKernel* kernel, |
| 92 | + const double _Complex* psi, |
| 93 | + KoreDiagnostics* out_diagnostics |
| 94 | +); |
| 95 | + |
| 96 | +void kore_version(uint32_t* major, uint32_t* minor, uint32_t* patch); |
| 97 | +``` |
| 98 | +
|
| 99 | +`out_summary` is nullable and should be documented as such in the header. |
| 100 | +
|
| 101 | +## Rust Layout And Header Generation |
| 102 | +
|
| 103 | +The FFI layer should live in a separate module such as `src/ffi.rs` or `src/ffi/mod.rs`. The numerical core remains in `src/sp/` and is not made C-aware. The FFI layer acts only as a translation boundary between POD C structs and the idiomatic Rust core API. |
| 104 | +
|
| 105 | +`cbindgen` should generate `include/kore.h` from the FFI layer, with a checked-in `cbindgen.toml` to keep generation reproducible. The checked-in header should begin with a generated-file banner such as: |
| 106 | +
|
| 107 | +```c |
| 108 | +/* |
| 109 | + * kore.h - generated by cbindgen, do not edit manually. |
| 110 | + * Regenerate with: cbindgen --config cbindgen.toml --output include/kore.h |
| 111 | + */ |
| 112 | +``` |
| 113 | + |
| 114 | +The ABI phase should also define a reproducible drift check: |
| 115 | + |
| 116 | +```bash |
| 117 | +cbindgen --config cbindgen.toml --output /tmp/kore_check.h |
| 118 | +diff include/kore.h /tmp/kore_check.h |
| 119 | +``` |
| 120 | + |
| 121 | +If full CI is not added in the same phase, this command should still be documented as a required pre-release check. |
| 122 | + |
| 123 | +`no_std` compatibility is explicitly out of scope for this ABI layer. The current kernel depends on allocation, FFT planning, and Rayon, so the right output of the compatibility check is documentation that this layer is not `no_std` by design. |
| 124 | + |
| 125 | +## Verification Strategy |
| 126 | + |
| 127 | +The ABI layer needs its own verification, not just trust in the Rust core. The implementation phase should cover: |
| 128 | + |
| 129 | +- Rust tests that call the `extern "C"` functions through raw pointers |
| 130 | +- null-pointer and invalid-argument checks |
| 131 | +- nullable output pointer behavior for status, summary, and version fields |
| 132 | +- diagnostics parity between the ABI and the native Rust API |
| 133 | +- step and step_n behavior on caller-owned buffers |
| 134 | +- version output sourced from Cargo package metadata |
| 135 | +- a tiny C compile/link smoke test against `include/kore.h` |
| 136 | +- header regeneration drift checks |
| 137 | + |
| 138 | +The ABI phase is only complete if the Rust tests still pass, the ABI boundary tests pass, the header regenerates cleanly, and `cargo clippy -- -D warnings` remains green. |
| 139 | + |
| 140 | +## Non-Goals |
| 141 | + |
| 142 | +- C-side buffer initialization helpers |
| 143 | +- exposing internal FFT or Poisson operators through the ABI |
| 144 | +- string-based error reporting |
| 145 | +- Python bindings, PyO3, or any higher-level language integration |
| 146 | +- no_std portability |
| 147 | + |
| 148 | +## Notes |
| 149 | + |
| 150 | +The Rust surface is now narrow enough to design this ABI cleanly. The ABI should remain a thin wrapper over that stabilized Rust API rather than freezing additional convenience semantics into the public C header. |
0 commit comments