Skip to content

Commit 095e38a

Browse files
P1: align Zig FFI symbol names to the Idris2 ABI (#39)
## Problem: ABI ↔ FFI symbol-name drift The Idris2 ABI in `src/interface/abi/Chapeliser/ABI/Foreign.idr` is the **source of truth** for the C-ABI contract. It declares 12 foreign symbols via `%foreign "C:c_*, libchapeliser_ffi"`: `c_init`, `c_shutdown`, `c_get_total_items`, `c_load_item`, `c_store_result`, `c_process_item`, `c_process_chunk`, `c_reduce`, `c_is_match`, `c_key_hash`, `c_checkpoint_save`, `c_checkpoint_load` The Zig FFI reference implementation in `src/interface/ffi/src/main.zig` instead exported these as `chapeliser_ref_<stem>`. The functions correspond 1:1 by stem and the `Result` codes already matched, but the **symbol names diverged**, so the ABI and FFI would not link — every `c_*` import the ABI declares would resolve to an undefined symbol. ## Fix (Zig-only; ABI unchanged because it is the source of truth) - Renamed the 12 ABI-declared exports from `chapeliser_ref_<stem>` to exactly `c_<stem>` in `src/interface/ffi/src/main.zig`. - The two extra utility functions (`version`, `build_info`) are **not** part of the ABI, so they are namespaced as `chapeliser_version` / `chapeliser_build_info` rather than given `c_` names. - Updated all internal call sites in `main.zig` tests and the `extern fn` declarations in `src/interface/ffi/test/integration_test.zig` to match. No behaviour or result-code values changed. Only files under `src/interface/ffi/` were touched. ## Verification - `zig build test` (main.zig unit tests, 7/7): **pass** - `zig build test-integration` (links the shared lib, so the test's `extern` decls resolve against the renamed exports): **pass** - `idris2 --build chapeliser-abi.ipkg`: **exit 0** - Cross-check: every `C:c_<name>` in `Foreign.idr` now has a matching `export fn c_<name>` in `main.zig`; no `chapeliser_ref_` references remain. Note: any rust-ci / Hypatia / governance red checks are pre-existing estate-infra issues unrelated to this Zig-only change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH --- _Generated by [Claude Code](https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 417cccd commit 095e38a

2 files changed

Lines changed: 64 additions & 64 deletions

File tree

src/interface/ffi/src/main.zig

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,14 @@ var state: ?ReferenceState = null;
7272
//==============================================================================
7373

7474
/// Initialise the reference workload. Returns 0 on success.
75-
export fn chapeliser_ref_init() callconv(.C) c_int {
75+
export fn c_init() callconv(.C) c_int {
7676
if (state != null) return @intFromEnum(Result.err); // already initialised
7777
state = ReferenceState.init(std.heap.c_allocator);
7878
return @intFromEnum(Result.ok);
7979
}
8080

8181
/// Shut down the reference workload. Returns 0 on success.
82-
export fn chapeliser_ref_shutdown() callconv(.C) c_int {
82+
export fn c_shutdown() callconv(.C) c_int {
8383
if (state) |*s| {
8484
s.deinit();
8585
state = null;
@@ -93,15 +93,15 @@ export fn chapeliser_ref_shutdown() callconv(.C) c_int {
9393
//==============================================================================
9494

9595
/// Return the total number of input items.
96-
export fn chapeliser_ref_get_total_items() callconv(.C) c_int {
96+
export fn c_get_total_items() callconv(.C) c_int {
9797
if (state) |s| {
9898
return @intCast(s.items.items.len);
9999
}
100100
return 0;
101101
}
102102

103103
/// Serialise input item `idx` into `buf`. Set `*len` to bytes written.
104-
export fn chapeliser_ref_load_item(idx: c_int, buf: [*]u8, len: *usize) callconv(.C) c_int {
104+
export fn c_load_item(idx: c_int, buf: [*]u8, len: *usize) callconv(.C) c_int {
105105
const s = &(state orelse return @intFromEnum(Result.err));
106106
const i: usize = @intCast(idx);
107107
if (i >= s.items.items.len) return @intFromEnum(Result.invalid_param);
@@ -115,7 +115,7 @@ export fn chapeliser_ref_load_item(idx: c_int, buf: [*]u8, len: *usize) callconv
115115
}
116116

117117
/// Receive a processed result at index `idx`.
118-
export fn chapeliser_ref_store_result(idx: c_int, buf: [*]const u8, len: usize) callconv(.C) c_int {
118+
export fn c_store_result(idx: c_int, buf: [*]const u8, len: usize) callconv(.C) c_int {
119119
const s = &(state orelse return @intFromEnum(Result.err));
120120

121121
// Copy the result data
@@ -139,7 +139,7 @@ export fn chapeliser_ref_store_result(idx: c_int, buf: [*]const u8, len: usize)
139139
//==============================================================================
140140

141141
/// Process a single item: for the reference implementation, just echo it back.
142-
export fn chapeliser_ref_process_item(
142+
export fn c_process_item(
143143
in_buf: [*]const u8,
144144
in_len: usize,
145145
out_buf: [*]u8,
@@ -152,7 +152,7 @@ export fn chapeliser_ref_process_item(
152152
}
153153

154154
/// Process a chunk of items: for reference, process each individually.
155-
export fn chapeliser_ref_process_chunk(
155+
export fn c_process_chunk(
156156
items_buf: [*]const u8,
157157
item_count: c_int,
158158
item_offsets: [*]const c_int,
@@ -178,7 +178,7 @@ export fn chapeliser_ref_process_chunk(
178178
//==============================================================================
179179

180180
/// Combine two results: for reference, concatenate them.
181-
export fn chapeliser_ref_reduce(
181+
export fn c_reduce(
182182
a_buf: [*]const u8,
183183
a_len: usize,
184184
b_buf: [*]const u8,
@@ -197,7 +197,7 @@ export fn chapeliser_ref_reduce(
197197
//==============================================================================
198198

199199
/// Check if a result matches: for reference, match if first byte is non-zero.
200-
export fn chapeliser_ref_is_match(buf: [*]const u8, len: usize) callconv(.C) c_int {
200+
export fn c_is_match(buf: [*]const u8, len: usize) callconv(.C) c_int {
201201
if (len == 0) return 0;
202202
return if (buf[0] != 0) 1 else 0;
203203
}
@@ -207,7 +207,7 @@ export fn chapeliser_ref_is_match(buf: [*]const u8, len: usize) callconv(.C) c_i
207207
//==============================================================================
208208

209209
/// Hash an item's key: for reference, use FNV-1a on the first 8 bytes.
210-
export fn chapeliser_ref_key_hash(buf: [*]const u8, len: usize) callconv(.C) c_uint {
210+
export fn c_key_hash(buf: [*]const u8, len: usize) callconv(.C) c_uint {
211211
const hash_len = @min(len, 8);
212212
var h: u32 = 2166136261; // FNV offset basis
213213
for (buf[0..hash_len]) |b| {
@@ -222,7 +222,7 @@ export fn chapeliser_ref_key_hash(buf: [*]const u8, len: usize) callconv(.C) c_u
222222
//==============================================================================
223223

224224
/// Save checkpoint: reference implementation is a no-op.
225-
export fn chapeliser_ref_checkpoint_save(
225+
export fn c_checkpoint_save(
226226
_: [*]const u8,
227227
_: usize,
228228
_: [*:0]const u8,
@@ -231,7 +231,7 @@ export fn chapeliser_ref_checkpoint_save(
231231
}
232232

233233
/// Load checkpoint: reference implementation is a no-op.
234-
export fn chapeliser_ref_checkpoint_load(
234+
export fn c_checkpoint_load(
235235
_: [*]u8,
236236
_: *usize,
237237
_: [*:0]const u8,
@@ -243,11 +243,11 @@ export fn chapeliser_ref_checkpoint_load(
243243
// Version
244244
//==============================================================================
245245

246-
export fn chapeliser_ref_version() callconv(.C) [*:0]const u8 {
246+
export fn chapeliser_version() callconv(.C) [*:0]const u8 {
247247
return VERSION.ptr;
248248
}
249249

250-
export fn chapeliser_ref_build_info() callconv(.C) [*:0]const u8 {
250+
export fn chapeliser_build_info() callconv(.C) [*:0]const u8 {
251251
return BUILD_INFO.ptr;
252252
}
253253

@@ -256,12 +256,12 @@ export fn chapeliser_ref_build_info() callconv(.C) [*:0]const u8 {
256256
//==============================================================================
257257

258258
test "lifecycle" {
259-
const rc_init = chapeliser_ref_init();
259+
const rc_init = c_init();
260260
try std.testing.expectEqual(@as(c_int, 0), rc_init);
261-
defer _ = chapeliser_ref_shutdown();
261+
defer _ = c_shutdown();
262262

263263
// Double init should fail
264-
const rc_double = chapeliser_ref_init();
264+
const rc_double = c_init();
265265
try std.testing.expectEqual(@as(c_int, 1), rc_double);
266266
}
267267

@@ -270,7 +270,7 @@ test "process_item echo" {
270270
var output: [256]u8 = undefined;
271271
var out_len: usize = 0;
272272

273-
const rc = chapeliser_ref_process_item(input.ptr, input.len, &output, &out_len);
273+
const rc = c_process_item(input.ptr, input.len, &output, &out_len);
274274
try std.testing.expectEqual(@as(c_int, 0), rc);
275275
try std.testing.expectEqual(input.len, out_len);
276276
try std.testing.expectEqualStrings(input, output[0..out_len]);
@@ -282,33 +282,33 @@ test "reduce concatenates" {
282282
var output: [256]u8 = undefined;
283283
var out_len: usize = 0;
284284

285-
const rc = chapeliser_ref_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len);
285+
const rc = c_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len);
286286
try std.testing.expectEqual(@as(c_int, 0), rc);
287287
try std.testing.expectEqualStrings("foobar", output[0..out_len]);
288288
}
289289

290290
test "key_hash deterministic" {
291291
const data = "testkey1";
292-
const h1 = chapeliser_ref_key_hash(data.ptr, data.len);
293-
const h2 = chapeliser_ref_key_hash(data.ptr, data.len);
292+
const h1 = c_key_hash(data.ptr, data.len);
293+
const h2 = c_key_hash(data.ptr, data.len);
294294
try std.testing.expectEqual(h1, h2);
295295
}
296296

297297
test "is_match" {
298298
const yes = [_]u8{ 0x42, 0x00 };
299299
const no = [_]u8{ 0x00, 0x42 };
300-
try std.testing.expectEqual(@as(c_int, 1), chapeliser_ref_is_match(&yes, yes.len));
301-
try std.testing.expectEqual(@as(c_int, 0), chapeliser_ref_is_match(&no, no.len));
300+
try std.testing.expectEqual(@as(c_int, 1), c_is_match(&yes, yes.len));
301+
try std.testing.expectEqual(@as(c_int, 0), c_is_match(&no, no.len));
302302
}
303303

304304
test "checkpoint not implemented" {
305305
var buf: [64]u8 = undefined;
306306
var len: usize = buf.len;
307-
try std.testing.expectEqual(@as(c_int, -1), chapeliser_ref_checkpoint_save(&buf, len, "test"));
308-
try std.testing.expectEqual(@as(c_int, -1), chapeliser_ref_checkpoint_load(&buf, &len, "test"));
307+
try std.testing.expectEqual(@as(c_int, -1), c_checkpoint_save(&buf, len, "test"));
308+
try std.testing.expectEqual(@as(c_int, -1), c_checkpoint_load(&buf, &len, "test"));
309309
}
310310

311311
test "version" {
312-
const ver = std.mem.span(chapeliser_ref_version());
312+
const ver = std.mem.span(chapeliser_version());
313313
try std.testing.expectEqualStrings("0.1.0", ver);
314314
}

src/interface/ffi/test/integration_test.zig

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,44 @@ const std = @import("std");
88
const testing = std.testing;
99

1010
// Import reference FFI functions
11-
extern fn chapeliser_ref_init() c_int;
12-
extern fn chapeliser_ref_shutdown() c_int;
13-
extern fn chapeliser_ref_get_total_items() c_int;
14-
extern fn chapeliser_ref_load_item(c_int, [*]u8, *usize) c_int;
15-
extern fn chapeliser_ref_store_result(c_int, [*]const u8, usize) c_int;
16-
extern fn chapeliser_ref_process_item([*]const u8, usize, [*]u8, *usize) c_int;
17-
extern fn chapeliser_ref_process_chunk([*]const u8, c_int, [*]const c_int, [*]const c_int, [*]u8, *usize) c_int;
18-
extern fn chapeliser_ref_reduce([*]const u8, usize, [*]const u8, usize, [*]u8, *usize) c_int;
19-
extern fn chapeliser_ref_is_match([*]const u8, usize) c_int;
20-
extern fn chapeliser_ref_key_hash([*]const u8, usize) c_uint;
21-
extern fn chapeliser_ref_checkpoint_save([*]const u8, usize, [*:0]const u8) c_int;
22-
extern fn chapeliser_ref_checkpoint_load([*]u8, *usize, [*:0]const u8) c_int;
23-
extern fn chapeliser_ref_version() [*:0]const u8;
24-
extern fn chapeliser_ref_build_info() [*:0]const u8;
11+
extern fn c_init() c_int;
12+
extern fn c_shutdown() c_int;
13+
extern fn c_get_total_items() c_int;
14+
extern fn c_load_item(c_int, [*]u8, *usize) c_int;
15+
extern fn c_store_result(c_int, [*]const u8, usize) c_int;
16+
extern fn c_process_item([*]const u8, usize, [*]u8, *usize) c_int;
17+
extern fn c_process_chunk([*]const u8, c_int, [*]const c_int, [*]const c_int, [*]u8, *usize) c_int;
18+
extern fn c_reduce([*]const u8, usize, [*]const u8, usize, [*]u8, *usize) c_int;
19+
extern fn c_is_match([*]const u8, usize) c_int;
20+
extern fn c_key_hash([*]const u8, usize) c_uint;
21+
extern fn c_checkpoint_save([*]const u8, usize, [*:0]const u8) c_int;
22+
extern fn c_checkpoint_load([*]u8, *usize, [*:0]const u8) c_int;
23+
extern fn chapeliser_version() [*:0]const u8;
24+
extern fn chapeliser_build_info() [*:0]const u8;
2525

2626
//==============================================================================
2727
// Lifecycle Tests
2828
//==============================================================================
2929

3030
test "init and shutdown" {
31-
const rc_init = chapeliser_ref_init();
31+
const rc_init = c_init();
3232
try testing.expectEqual(@as(c_int, 0), rc_init);
3333

34-
const rc_shutdown = chapeliser_ref_shutdown();
34+
const rc_shutdown = c_shutdown();
3535
try testing.expectEqual(@as(c_int, 0), rc_shutdown);
3636
}
3737

3838
test "double init fails" {
39-
const rc1 = chapeliser_ref_init();
39+
const rc1 = c_init();
4040
try testing.expectEqual(@as(c_int, 0), rc1);
41-
defer _ = chapeliser_ref_shutdown();
41+
defer _ = c_shutdown();
4242

43-
const rc2 = chapeliser_ref_init();
43+
const rc2 = c_init();
4444
try testing.expectEqual(@as(c_int, 1), rc2); // error: already initialised
4545
}
4646

4747
test "shutdown without init fails" {
48-
const rc = chapeliser_ref_shutdown();
48+
const rc = c_shutdown();
4949
try testing.expectEqual(@as(c_int, 1), rc); // error: not initialised
5050
}
5151

@@ -58,7 +58,7 @@ test "process_item preserves data" {
5858
var output: [256]u8 = undefined;
5959
var out_len: usize = 0;
6060

61-
const rc = chapeliser_ref_process_item(input.ptr, input.len, &output, &out_len);
61+
const rc = c_process_item(input.ptr, input.len, &output, &out_len);
6262
try testing.expectEqual(@as(c_int, 0), rc);
6363
try testing.expectEqual(input.len, out_len);
6464
try testing.expectEqualStrings(input, output[0..out_len]);
@@ -69,7 +69,7 @@ test "process_item handles empty input" {
6969
var output: [256]u8 = undefined;
7070
var out_len: usize = 0;
7171

72-
const rc = chapeliser_ref_process_item(input.ptr, input.len, &output, &out_len);
72+
const rc = c_process_item(input.ptr, input.len, &output, &out_len);
7373
try testing.expectEqual(@as(c_int, 0), rc);
7474
try testing.expectEqual(@as(usize, 0), out_len);
7575
}
@@ -84,7 +84,7 @@ test "reduce combines two results" {
8484
var output: [256]u8 = undefined;
8585
var out_len: usize = 0;
8686

87-
const rc = chapeliser_ref_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len);
87+
const rc = c_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len);
8888
try testing.expectEqual(@as(c_int, 0), rc);
8989
try testing.expectEqualStrings("hello world", output[0..out_len]);
9090
}
@@ -104,12 +104,12 @@ test "reduce is associative for concatenation" {
104104
var final2: usize = 0;
105105

106106
// (a + b) + c
107-
_ = chapeliser_ref_reduce(a.ptr, a.len, b.ptr, b.len, &tmp1, &len1);
108-
_ = chapeliser_ref_reduce(&tmp1, len1, c.ptr, c.len, &result1, &final1);
107+
_ = c_reduce(a.ptr, a.len, b.ptr, b.len, &tmp1, &len1);
108+
_ = c_reduce(&tmp1, len1, c.ptr, c.len, &result1, &final1);
109109

110110
// a + (b + c)
111-
_ = chapeliser_ref_reduce(b.ptr, b.len, c.ptr, c.len, &tmp2, &len2);
112-
_ = chapeliser_ref_reduce(a.ptr, a.len, &tmp2, len2, &result2, &final2);
111+
_ = c_reduce(b.ptr, b.len, c.ptr, c.len, &tmp2, &len2);
112+
_ = c_reduce(a.ptr, a.len, &tmp2, len2, &result2, &final2);
113113

114114
try testing.expectEqualStrings(result1[0..final1], result2[0..final2]);
115115
}
@@ -120,17 +120,17 @@ test "reduce is associative for concatenation" {
120120

121121
test "is_match returns 1 for non-zero first byte" {
122122
const data = [_]u8{ 0xFF, 0x00, 0x00 };
123-
try testing.expectEqual(@as(c_int, 1), chapeliser_ref_is_match(&data, data.len));
123+
try testing.expectEqual(@as(c_int, 1), c_is_match(&data, data.len));
124124
}
125125

126126
test "is_match returns 0 for zero first byte" {
127127
const data = [_]u8{ 0x00, 0xFF, 0xFF };
128-
try testing.expectEqual(@as(c_int, 0), chapeliser_ref_is_match(&data, data.len));
128+
try testing.expectEqual(@as(c_int, 0), c_is_match(&data, data.len));
129129
}
130130

131131
test "is_match returns 0 for empty buffer" {
132132
const data = [_]u8{};
133-
try testing.expectEqual(@as(c_int, 0), chapeliser_ref_is_match(&data, 0));
133+
try testing.expectEqual(@as(c_int, 0), c_is_match(&data, 0));
134134
}
135135

136136
//==============================================================================
@@ -139,16 +139,16 @@ test "is_match returns 0 for empty buffer" {
139139

140140
test "key_hash is deterministic" {
141141
const key = "partition-key";
142-
const h1 = chapeliser_ref_key_hash(key.ptr, key.len);
143-
const h2 = chapeliser_ref_key_hash(key.ptr, key.len);
142+
const h1 = c_key_hash(key.ptr, key.len);
143+
const h2 = c_key_hash(key.ptr, key.len);
144144
try testing.expectEqual(h1, h2);
145145
}
146146

147147
test "key_hash differs for different keys" {
148148
const k1 = "key-alpha";
149149
const k2 = "key-bravo";
150-
const h1 = chapeliser_ref_key_hash(k1.ptr, k1.len);
151-
const h2 = chapeliser_ref_key_hash(k2.ptr, k2.len);
150+
const h1 = c_key_hash(k1.ptr, k1.len);
151+
const h2 = c_key_hash(k2.ptr, k2.len);
152152
try testing.expect(h1 != h2);
153153
}
154154

@@ -158,14 +158,14 @@ test "key_hash differs for different keys" {
158158

159159
test "checkpoint save returns not-implemented" {
160160
const data = "checkpoint data";
161-
const rc = chapeliser_ref_checkpoint_save(data.ptr, data.len, "locale-0");
161+
const rc = c_checkpoint_save(data.ptr, data.len, "locale-0");
162162
try testing.expectEqual(@as(c_int, -1), rc);
163163
}
164164

165165
test "checkpoint load returns not-implemented" {
166166
var buf: [256]u8 = undefined;
167167
var len: usize = buf.len;
168-
const rc = chapeliser_ref_checkpoint_load(&buf, &len, "locale-0");
168+
const rc = c_checkpoint_load(&buf, &len, "locale-0");
169169
try testing.expectEqual(@as(c_int, -1), rc);
170170
}
171171

@@ -174,13 +174,13 @@ test "checkpoint load returns not-implemented" {
174174
//==============================================================================
175175

176176
test "version is semantic" {
177-
const ver = std.mem.span(chapeliser_ref_version());
177+
const ver = std.mem.span(chapeliser_version());
178178
try testing.expect(ver.len > 0);
179179
try testing.expect(std.mem.count(u8, ver, ".") >= 1);
180180
}
181181

182182
test "build_info is not empty" {
183-
const info = std.mem.span(chapeliser_ref_build_info());
183+
const info = std.mem.span(chapeliser_build_info());
184184
try testing.expect(info.len > 0);
185185
try testing.expect(std.mem.indexOf(u8, info, "chapeliser") != null);
186186
}

0 commit comments

Comments
 (0)