From d0d9bde3f91573d5239f325060f6f1555fa85977 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 26 Jun 2026 22:03:35 +0000 Subject: [PATCH] P1: align Zig FFI symbol names to the Idris2 ABI 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 exported these as `chapeliser_ref_` instead. The functions correspond 1:1 by stem and the Result codes already match, but the symbol names diverged, so the ABI and FFI would not link (every `c_*` import would be undefined). Fix (Zig-only, ABI unchanged because it is the source of truth): - Rename the 12 ABI-declared exports from `chapeliser_ref_` to exactly `c_` 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` and `chapeliser_build_info` rather than given `c_` names. - Update all internal call sites in main.zig tests and the `extern fn` declarations in test/integration_test.zig accordingly. No behaviour or result-code values changed. Verification: - `zig build test` (main.zig unit tests): pass - `zig build test-integration` (links the lib, resolves externs): pass - `idris2 --build chapeliser-abi.ipkg`: exit 0 - Every `C:c_` in Foreign.idr now has a matching `export fn c_` in main.zig. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_019xMKB3T4Vo5FYC7Czx3JSH --- src/interface/ffi/src/main.zig | 52 +++++++------- src/interface/ffi/test/integration_test.zig | 76 ++++++++++----------- 2 files changed, 64 insertions(+), 64 deletions(-) diff --git a/src/interface/ffi/src/main.zig b/src/interface/ffi/src/main.zig index b1fcc7b..8b81bb6 100644 --- a/src/interface/ffi/src/main.zig +++ b/src/interface/ffi/src/main.zig @@ -72,14 +72,14 @@ var state: ?ReferenceState = null; //============================================================================== /// Initialise the reference workload. Returns 0 on success. -export fn chapeliser_ref_init() callconv(.C) c_int { +export fn c_init() callconv(.C) c_int { if (state != null) return @intFromEnum(Result.err); // already initialised state = ReferenceState.init(std.heap.c_allocator); return @intFromEnum(Result.ok); } /// Shut down the reference workload. Returns 0 on success. -export fn chapeliser_ref_shutdown() callconv(.C) c_int { +export fn c_shutdown() callconv(.C) c_int { if (state) |*s| { s.deinit(); state = null; @@ -93,7 +93,7 @@ export fn chapeliser_ref_shutdown() callconv(.C) c_int { //============================================================================== /// Return the total number of input items. -export fn chapeliser_ref_get_total_items() callconv(.C) c_int { +export fn c_get_total_items() callconv(.C) c_int { if (state) |s| { return @intCast(s.items.items.len); } @@ -101,7 +101,7 @@ export fn chapeliser_ref_get_total_items() callconv(.C) c_int { } /// Serialise input item `idx` into `buf`. Set `*len` to bytes written. -export fn chapeliser_ref_load_item(idx: c_int, buf: [*]u8, len: *usize) callconv(.C) c_int { +export fn c_load_item(idx: c_int, buf: [*]u8, len: *usize) callconv(.C) c_int { const s = &(state orelse return @intFromEnum(Result.err)); const i: usize = @intCast(idx); 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 } /// Receive a processed result at index `idx`. -export fn chapeliser_ref_store_result(idx: c_int, buf: [*]const u8, len: usize) callconv(.C) c_int { +export fn c_store_result(idx: c_int, buf: [*]const u8, len: usize) callconv(.C) c_int { const s = &(state orelse return @intFromEnum(Result.err)); // Copy the result data @@ -139,7 +139,7 @@ export fn chapeliser_ref_store_result(idx: c_int, buf: [*]const u8, len: usize) //============================================================================== /// Process a single item: for the reference implementation, just echo it back. -export fn chapeliser_ref_process_item( +export fn c_process_item( in_buf: [*]const u8, in_len: usize, out_buf: [*]u8, @@ -152,7 +152,7 @@ export fn chapeliser_ref_process_item( } /// Process a chunk of items: for reference, process each individually. -export fn chapeliser_ref_process_chunk( +export fn c_process_chunk( items_buf: [*]const u8, item_count: c_int, item_offsets: [*]const c_int, @@ -178,7 +178,7 @@ export fn chapeliser_ref_process_chunk( //============================================================================== /// Combine two results: for reference, concatenate them. -export fn chapeliser_ref_reduce( +export fn c_reduce( a_buf: [*]const u8, a_len: usize, b_buf: [*]const u8, @@ -197,7 +197,7 @@ export fn chapeliser_ref_reduce( //============================================================================== /// Check if a result matches: for reference, match if first byte is non-zero. -export fn chapeliser_ref_is_match(buf: [*]const u8, len: usize) callconv(.C) c_int { +export fn c_is_match(buf: [*]const u8, len: usize) callconv(.C) c_int { if (len == 0) return 0; return if (buf[0] != 0) 1 else 0; } @@ -207,7 +207,7 @@ export fn chapeliser_ref_is_match(buf: [*]const u8, len: usize) callconv(.C) c_i //============================================================================== /// Hash an item's key: for reference, use FNV-1a on the first 8 bytes. -export fn chapeliser_ref_key_hash(buf: [*]const u8, len: usize) callconv(.C) c_uint { +export fn c_key_hash(buf: [*]const u8, len: usize) callconv(.C) c_uint { const hash_len = @min(len, 8); var h: u32 = 2166136261; // FNV offset basis 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 //============================================================================== /// Save checkpoint: reference implementation is a no-op. -export fn chapeliser_ref_checkpoint_save( +export fn c_checkpoint_save( _: [*]const u8, _: usize, _: [*:0]const u8, @@ -231,7 +231,7 @@ export fn chapeliser_ref_checkpoint_save( } /// Load checkpoint: reference implementation is a no-op. -export fn chapeliser_ref_checkpoint_load( +export fn c_checkpoint_load( _: [*]u8, _: *usize, _: [*:0]const u8, @@ -243,11 +243,11 @@ export fn chapeliser_ref_checkpoint_load( // Version //============================================================================== -export fn chapeliser_ref_version() callconv(.C) [*:0]const u8 { +export fn chapeliser_version() callconv(.C) [*:0]const u8 { return VERSION.ptr; } -export fn chapeliser_ref_build_info() callconv(.C) [*:0]const u8 { +export fn chapeliser_build_info() callconv(.C) [*:0]const u8 { return BUILD_INFO.ptr; } @@ -256,12 +256,12 @@ export fn chapeliser_ref_build_info() callconv(.C) [*:0]const u8 { //============================================================================== test "lifecycle" { - const rc_init = chapeliser_ref_init(); + const rc_init = c_init(); try std.testing.expectEqual(@as(c_int, 0), rc_init); - defer _ = chapeliser_ref_shutdown(); + defer _ = c_shutdown(); // Double init should fail - const rc_double = chapeliser_ref_init(); + const rc_double = c_init(); try std.testing.expectEqual(@as(c_int, 1), rc_double); } @@ -270,7 +270,7 @@ test "process_item echo" { var output: [256]u8 = undefined; var out_len: usize = 0; - const rc = chapeliser_ref_process_item(input.ptr, input.len, &output, &out_len); + const rc = c_process_item(input.ptr, input.len, &output, &out_len); try std.testing.expectEqual(@as(c_int, 0), rc); try std.testing.expectEqual(input.len, out_len); try std.testing.expectEqualStrings(input, output[0..out_len]); @@ -282,33 +282,33 @@ test "reduce concatenates" { var output: [256]u8 = undefined; var out_len: usize = 0; - const rc = chapeliser_ref_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len); + const rc = c_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len); try std.testing.expectEqual(@as(c_int, 0), rc); try std.testing.expectEqualStrings("foobar", output[0..out_len]); } test "key_hash deterministic" { const data = "testkey1"; - const h1 = chapeliser_ref_key_hash(data.ptr, data.len); - const h2 = chapeliser_ref_key_hash(data.ptr, data.len); + const h1 = c_key_hash(data.ptr, data.len); + const h2 = c_key_hash(data.ptr, data.len); try std.testing.expectEqual(h1, h2); } test "is_match" { const yes = [_]u8{ 0x42, 0x00 }; const no = [_]u8{ 0x00, 0x42 }; - try std.testing.expectEqual(@as(c_int, 1), chapeliser_ref_is_match(&yes, yes.len)); - try std.testing.expectEqual(@as(c_int, 0), chapeliser_ref_is_match(&no, no.len)); + try std.testing.expectEqual(@as(c_int, 1), c_is_match(&yes, yes.len)); + try std.testing.expectEqual(@as(c_int, 0), c_is_match(&no, no.len)); } test "checkpoint not implemented" { var buf: [64]u8 = undefined; var len: usize = buf.len; - try std.testing.expectEqual(@as(c_int, -1), chapeliser_ref_checkpoint_save(&buf, len, "test")); - try std.testing.expectEqual(@as(c_int, -1), chapeliser_ref_checkpoint_load(&buf, &len, "test")); + try std.testing.expectEqual(@as(c_int, -1), c_checkpoint_save(&buf, len, "test")); + try std.testing.expectEqual(@as(c_int, -1), c_checkpoint_load(&buf, &len, "test")); } test "version" { - const ver = std.mem.span(chapeliser_ref_version()); + const ver = std.mem.span(chapeliser_version()); try std.testing.expectEqualStrings("0.1.0", ver); } diff --git a/src/interface/ffi/test/integration_test.zig b/src/interface/ffi/test/integration_test.zig index 53e078b..5d5c30f 100644 --- a/src/interface/ffi/test/integration_test.zig +++ b/src/interface/ffi/test/integration_test.zig @@ -8,44 +8,44 @@ const std = @import("std"); const testing = std.testing; // Import reference FFI functions -extern fn chapeliser_ref_init() c_int; -extern fn chapeliser_ref_shutdown() c_int; -extern fn chapeliser_ref_get_total_items() c_int; -extern fn chapeliser_ref_load_item(c_int, [*]u8, *usize) c_int; -extern fn chapeliser_ref_store_result(c_int, [*]const u8, usize) c_int; -extern fn chapeliser_ref_process_item([*]const u8, usize, [*]u8, *usize) c_int; -extern fn chapeliser_ref_process_chunk([*]const u8, c_int, [*]const c_int, [*]const c_int, [*]u8, *usize) c_int; -extern fn chapeliser_ref_reduce([*]const u8, usize, [*]const u8, usize, [*]u8, *usize) c_int; -extern fn chapeliser_ref_is_match([*]const u8, usize) c_int; -extern fn chapeliser_ref_key_hash([*]const u8, usize) c_uint; -extern fn chapeliser_ref_checkpoint_save([*]const u8, usize, [*:0]const u8) c_int; -extern fn chapeliser_ref_checkpoint_load([*]u8, *usize, [*:0]const u8) c_int; -extern fn chapeliser_ref_version() [*:0]const u8; -extern fn chapeliser_ref_build_info() [*:0]const u8; +extern fn c_init() c_int; +extern fn c_shutdown() c_int; +extern fn c_get_total_items() c_int; +extern fn c_load_item(c_int, [*]u8, *usize) c_int; +extern fn c_store_result(c_int, [*]const u8, usize) c_int; +extern fn c_process_item([*]const u8, usize, [*]u8, *usize) c_int; +extern fn c_process_chunk([*]const u8, c_int, [*]const c_int, [*]const c_int, [*]u8, *usize) c_int; +extern fn c_reduce([*]const u8, usize, [*]const u8, usize, [*]u8, *usize) c_int; +extern fn c_is_match([*]const u8, usize) c_int; +extern fn c_key_hash([*]const u8, usize) c_uint; +extern fn c_checkpoint_save([*]const u8, usize, [*:0]const u8) c_int; +extern fn c_checkpoint_load([*]u8, *usize, [*:0]const u8) c_int; +extern fn chapeliser_version() [*:0]const u8; +extern fn chapeliser_build_info() [*:0]const u8; //============================================================================== // Lifecycle Tests //============================================================================== test "init and shutdown" { - const rc_init = chapeliser_ref_init(); + const rc_init = c_init(); try testing.expectEqual(@as(c_int, 0), rc_init); - const rc_shutdown = chapeliser_ref_shutdown(); + const rc_shutdown = c_shutdown(); try testing.expectEqual(@as(c_int, 0), rc_shutdown); } test "double init fails" { - const rc1 = chapeliser_ref_init(); + const rc1 = c_init(); try testing.expectEqual(@as(c_int, 0), rc1); - defer _ = chapeliser_ref_shutdown(); + defer _ = c_shutdown(); - const rc2 = chapeliser_ref_init(); + const rc2 = c_init(); try testing.expectEqual(@as(c_int, 1), rc2); // error: already initialised } test "shutdown without init fails" { - const rc = chapeliser_ref_shutdown(); + const rc = c_shutdown(); try testing.expectEqual(@as(c_int, 1), rc); // error: not initialised } @@ -58,7 +58,7 @@ test "process_item preserves data" { var output: [256]u8 = undefined; var out_len: usize = 0; - const rc = chapeliser_ref_process_item(input.ptr, input.len, &output, &out_len); + const rc = c_process_item(input.ptr, input.len, &output, &out_len); try testing.expectEqual(@as(c_int, 0), rc); try testing.expectEqual(input.len, out_len); try testing.expectEqualStrings(input, output[0..out_len]); @@ -69,7 +69,7 @@ test "process_item handles empty input" { var output: [256]u8 = undefined; var out_len: usize = 0; - const rc = chapeliser_ref_process_item(input.ptr, input.len, &output, &out_len); + const rc = c_process_item(input.ptr, input.len, &output, &out_len); try testing.expectEqual(@as(c_int, 0), rc); try testing.expectEqual(@as(usize, 0), out_len); } @@ -84,7 +84,7 @@ test "reduce combines two results" { var output: [256]u8 = undefined; var out_len: usize = 0; - const rc = chapeliser_ref_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len); + const rc = c_reduce(a.ptr, a.len, b.ptr, b.len, &output, &out_len); try testing.expectEqual(@as(c_int, 0), rc); try testing.expectEqualStrings("hello world", output[0..out_len]); } @@ -104,12 +104,12 @@ test "reduce is associative for concatenation" { var final2: usize = 0; // (a + b) + c - _ = chapeliser_ref_reduce(a.ptr, a.len, b.ptr, b.len, &tmp1, &len1); - _ = chapeliser_ref_reduce(&tmp1, len1, c.ptr, c.len, &result1, &final1); + _ = c_reduce(a.ptr, a.len, b.ptr, b.len, &tmp1, &len1); + _ = c_reduce(&tmp1, len1, c.ptr, c.len, &result1, &final1); // a + (b + c) - _ = chapeliser_ref_reduce(b.ptr, b.len, c.ptr, c.len, &tmp2, &len2); - _ = chapeliser_ref_reduce(a.ptr, a.len, &tmp2, len2, &result2, &final2); + _ = c_reduce(b.ptr, b.len, c.ptr, c.len, &tmp2, &len2); + _ = c_reduce(a.ptr, a.len, &tmp2, len2, &result2, &final2); try testing.expectEqualStrings(result1[0..final1], result2[0..final2]); } @@ -120,17 +120,17 @@ test "reduce is associative for concatenation" { test "is_match returns 1 for non-zero first byte" { const data = [_]u8{ 0xFF, 0x00, 0x00 }; - try testing.expectEqual(@as(c_int, 1), chapeliser_ref_is_match(&data, data.len)); + try testing.expectEqual(@as(c_int, 1), c_is_match(&data, data.len)); } test "is_match returns 0 for zero first byte" { const data = [_]u8{ 0x00, 0xFF, 0xFF }; - try testing.expectEqual(@as(c_int, 0), chapeliser_ref_is_match(&data, data.len)); + try testing.expectEqual(@as(c_int, 0), c_is_match(&data, data.len)); } test "is_match returns 0 for empty buffer" { const data = [_]u8{}; - try testing.expectEqual(@as(c_int, 0), chapeliser_ref_is_match(&data, 0)); + try testing.expectEqual(@as(c_int, 0), c_is_match(&data, 0)); } //============================================================================== @@ -139,16 +139,16 @@ test "is_match returns 0 for empty buffer" { test "key_hash is deterministic" { const key = "partition-key"; - const h1 = chapeliser_ref_key_hash(key.ptr, key.len); - const h2 = chapeliser_ref_key_hash(key.ptr, key.len); + const h1 = c_key_hash(key.ptr, key.len); + const h2 = c_key_hash(key.ptr, key.len); try testing.expectEqual(h1, h2); } test "key_hash differs for different keys" { const k1 = "key-alpha"; const k2 = "key-bravo"; - const h1 = chapeliser_ref_key_hash(k1.ptr, k1.len); - const h2 = chapeliser_ref_key_hash(k2.ptr, k2.len); + const h1 = c_key_hash(k1.ptr, k1.len); + const h2 = c_key_hash(k2.ptr, k2.len); try testing.expect(h1 != h2); } @@ -158,14 +158,14 @@ test "key_hash differs for different keys" { test "checkpoint save returns not-implemented" { const data = "checkpoint data"; - const rc = chapeliser_ref_checkpoint_save(data.ptr, data.len, "locale-0"); + const rc = c_checkpoint_save(data.ptr, data.len, "locale-0"); try testing.expectEqual(@as(c_int, -1), rc); } test "checkpoint load returns not-implemented" { var buf: [256]u8 = undefined; var len: usize = buf.len; - const rc = chapeliser_ref_checkpoint_load(&buf, &len, "locale-0"); + const rc = c_checkpoint_load(&buf, &len, "locale-0"); try testing.expectEqual(@as(c_int, -1), rc); } @@ -174,13 +174,13 @@ test "checkpoint load returns not-implemented" { //============================================================================== test "version is semantic" { - const ver = std.mem.span(chapeliser_ref_version()); + const ver = std.mem.span(chapeliser_version()); try testing.expect(ver.len > 0); try testing.expect(std.mem.count(u8, ver, ".") >= 1); } test "build_info is not empty" { - const info = std.mem.span(chapeliser_ref_build_info()); + const info = std.mem.span(chapeliser_build_info()); try testing.expect(info.len > 0); try testing.expect(std.mem.indexOf(u8, info, "chapeliser") != null); }