Skip to content

Commit 351efbc

Browse files
committed
feat: stable CPU kernel + C ABI (v0.1.0-dev)
- Grid3, State3, SpKernel3, Strang split-step Schrödinger-Poisson - RustFFT backend, Rayon pointwise threading, f64 throughout - Stable C ABI: kore_create/destroy/step/step_n/diagnostics/version - Generated include/kore.h via cbindgen, checked in and drift-verified - Full test suite: unit, integration, FFI, C smoke (c11 -pedantic-errors) - cargo clippy -D warnings clean
0 parents  commit 351efbc

42 files changed

Lines changed: 4250 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.worktrees/
2+
target/
3+
.DS_Store
4+
Cargo.lock

Cargo.toml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "kore"
3+
version = "0.1.0"
4+
edition = "2024"
5+
description = "Portable FFT-based Schrodinger-Poisson pseudospectral kernel"
6+
license = "MIT OR Apache-2.0"
7+
repository = "https://github.com/edengilbertus/kore"
8+
9+
[lib]
10+
name = "kore"
11+
crate-type = ["rlib", "staticlib"]
12+
13+
[dependencies]
14+
num-complex = "0.4"
15+
rayon = "1"
16+
rustfft = "6"
17+
18+
[dev-dependencies]
19+
criterion = "0.6"
20+
21+
[[bench]]
22+
name = "step"
23+
harness = false

benches/step.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
use std::hint::black_box;
2+
3+
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
4+
use kore::{config::SpConfig, sp::kernel::SpKernel3, state::State3, Grid3};
5+
6+
fn bench_single_step(c: &mut Criterion) {
7+
let grid = Grid3::new([16, 16, 16], [1.0, 1.0, 1.0]).unwrap();
8+
let config = SpConfig::default();
9+
let mut kernel = SpKernel3::new(grid.clone(), config).unwrap();
10+
11+
c.bench_function("spkernel3/step/16^3", |b| {
12+
b.iter_batched(
13+
|| State3::plane_wave(&grid, [1, 2, 3]),
14+
|mut state| {
15+
kernel.step(black_box(&mut state)).unwrap();
16+
},
17+
BatchSize::SmallInput,
18+
);
19+
});
20+
}
21+
22+
criterion_group!(benches, bench_single_step);
23+
criterion_main!(benches);

cbindgen.toml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
language = "C"
2+
header = "/*\n * kore.h - generated by cbindgen, do not edit manually.\n * Regenerate with: cbindgen --config cbindgen.toml --output include/kore.h\n */"
3+
include_guard = "KORE_H"
4+
pragma_once = true
5+
documentation = false
6+
no_includes = true
7+
sys_includes = ["complex.h"]
8+
includes = ["stdint.h"]
9+
after_includes = "typedef struct KoreKernel KoreKernel;"
10+
11+
[parse]
12+
parse_deps = false
13+
include = ["kore"]
14+
15+
[export]
16+
include = [
17+
"KoreStatus",
18+
"KoreGrid",
19+
"KoreConfig",
20+
"KoreDiagnostics",
21+
"KoreDiagnosticsSummary",
22+
"kore_create",
23+
"kore_destroy",
24+
"kore_version",
25+
"kore_diagnostics",
26+
"kore_step",
27+
"kore_step_n",
28+
]
29+
item_types = ["enums", "structs", "functions", "typedefs"]
30+
renaming_overrides_prefixing = true
31+
32+
[export.rename]
33+
"Complex64" = "double _Complex"

docs/ffi-verification.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# C ABI Verification
2+
3+
Run this before any release or ABI-significant change:
4+
5+
```bash
6+
./scripts/verify_c_abi.sh
7+
```
8+
9+
That script performs two checks:
10+
11+
1. Regenerate a temporary header and diff it against `include/kore.h`
12+
2. Build the Rust static library and compile the C smoke test against `include/kore.h`
13+
14+
Equivalent manual commands:
15+
16+
```bash
17+
cbindgen --config cbindgen.toml --output /tmp/kore_check.h && diff include/kore.h /tmp/kore_check.h
18+
cargo build && cc -Iinclude tests/c_smoke.c target/debug/libkore.a -o /tmp/kore_c_smoke
19+
```
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
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

Comments
 (0)