From 10f5a4318d5f487e1d8621e2c27db57276c01b3d Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 12 Jun 2026 16:43:42 +0300 Subject: [PATCH] =?UTF-8?q?test(C1.1):=20cSourceCacheKey=20=E2=80=94=20con?= =?UTF-8?q?tent=20key=20for=20compiled=20#source=20units?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source bytes, declared-header CONTENT (header edits invalidate), defines/flags/include dirs in order, LLVM version, and target triple/sysroot all participate; section tags keep equal strings in different roles distinct. Pure function + variance property tests; nothing consumes it yet. --- src/c_import.test.zig | 68 +++++++++++++++++++++++++++++++++++++++++++ src/c_import.zig | 44 ++++++++++++++++++++++++++++ src/root.zig | 1 + 3 files changed, 113 insertions(+) create mode 100644 src/c_import.test.zig diff --git a/src/c_import.test.zig b/src/c_import.test.zig new file mode 100644 index 0000000..e889b89 --- /dev/null +++ b/src/c_import.test.zig @@ -0,0 +1,68 @@ +const std = @import("std"); +const c_import = @import("c_import.zig"); + +const SRC = "int f(void) { return 1; }"; +const HDR = "int f(void);"; +const VER = "19.1.7"; + +const none: []const []const u8 = &.{}; + +fn baseKey() u64 { + return c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=1"}, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); +} + +test "cSourceCacheKey: stable when nothing changes" { + try std.testing.expectEqual(baseKey(), baseKey()); +} + +test "cSourceCacheKey: source bytes vary the key" { + const other = c_import.cSourceCacheKey("int f(void) { return 2; }", &.{HDR}, &.{"A=1"}, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != other); +} + +test "cSourceCacheKey: declared header content varies the key" { + const other = c_import.cSourceCacheKey(SRC, &.{"int f(void); int g(void);"}, &.{"A=1"}, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != other); +} + +test "cSourceCacheKey: defines vary the key (value and order)" { + const v2 = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=2"}, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != v2); + + const ab = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{ "A=1", "B=1" }, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); + const ba = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{ "B=1", "A=1" }, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); + try std.testing.expect(ab != ba); +} + +test "cSourceCacheKey: flags vary the key" { + const other = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=1"}, &.{"-O3"}, &.{"inc"}, VER, "arm64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != other); +} + +test "cSourceCacheKey: a define is not a flag (same string, different role)" { + const as_define = c_import.cSourceCacheKey(SRC, none, &.{"X"}, none, none, VER, null, null); + const as_flag = c_import.cSourceCacheKey(SRC, none, none, &.{"X"}, none, VER, null, null); + try std.testing.expect(as_define != as_flag); +} + +test "cSourceCacheKey: include dirs vary the key" { + const other = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=1"}, &.{"-O2"}, &.{"other"}, VER, "arm64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != other); +} + +test "cSourceCacheKey: llvm version varies the key" { + const other = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=1"}, &.{"-O2"}, &.{"inc"}, "20.0.0", "arm64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != other); +} + +test "cSourceCacheKey: triple and sysroot vary the key; absent is not empty" { + const other_triple = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=1"}, &.{"-O2"}, &.{"inc"}, VER, "x86_64-apple-darwin", "/sdk"); + try std.testing.expect(baseKey() != other_triple); + + const other_sysroot = c_import.cSourceCacheKey(SRC, &.{HDR}, &.{"A=1"}, &.{"-O2"}, &.{"inc"}, VER, "arm64-apple-darwin", "/ndk"); + try std.testing.expect(baseKey() != other_sysroot); + + const absent = c_import.cSourceCacheKey(SRC, none, none, none, none, VER, null, null); + const empty = c_import.cSourceCacheKey(SRC, none, none, none, none, VER, "", ""); + try std.testing.expect(absent != empty); +} diff --git a/src/c_import.zig b/src/c_import.zig index 82cf1d7..79319f1 100644 --- a/src/c_import.zig +++ b/src/c_import.zig @@ -33,6 +33,50 @@ pub const CImportInfo = struct { flags: []const []const u8, }; +/// Cache key for one compiled `#source` member. Everything that can +/// change the produced object participates: the source bytes, the +/// unit's declared `#include` headers BY CONTENT (editing a declared +/// header invalidates), defines / flags / include dirs in declaration +/// order, the LLVM version, and the cross-target (triple + sysroot). +/// Section tags keep equal strings in different roles distinct (a +/// define never aliases a flag, an absent triple never aliases an +/// empty one). Transitive includes of the .c itself do NOT +/// participate — the block's declared surface is the invalidation +/// boundary. +pub fn cSourceCacheKey( + source_bytes: []const u8, + header_bytes: []const []const u8, + defines: []const []const u8, + flags: []const []const u8, + include_dirs: []const []const u8, + llvm_version: []const u8, + triple: ?[]const u8, + sysroot: ?[]const u8, +) u64 { + const Wyhash = std.hash.Wyhash; + var key = Wyhash.hash(0, "sx-c-import-v1"); + key = Wyhash.hash(key, source_bytes); + key = Wyhash.hash(key, "\x01headers"); + for (header_bytes) |hb| key = Wyhash.hash(key, hb); + key = Wyhash.hash(key, "\x01defines"); + for (defines) |d| key = Wyhash.hash(key, d); + key = Wyhash.hash(key, "\x01flags"); + for (flags) |f| key = Wyhash.hash(key, f); + key = Wyhash.hash(key, "\x01incdirs"); + for (include_dirs) |inc| key = Wyhash.hash(key, inc); + key = Wyhash.hash(key, "\x01llvm"); + key = Wyhash.hash(key, llvm_version); + if (triple) |t| { + key = Wyhash.hash(key, "\x01triple"); + key = Wyhash.hash(key, t); + } + if (sysroot) |sr| { + key = Wyhash.hash(key, "\x01sysroot"); + key = Wyhash.hash(key, sr); + } + return key; +} + /// Handle returned from loadCObjectsForJIT — caller must call unload() after JIT. pub const CImportHandle = struct { dylib_handle: ?*anyopaque = null, diff --git a/src/root.zig b/src/root.zig index 767d19f..a1f6774 100644 --- a/src/root.zig +++ b/src/root.zig @@ -16,6 +16,7 @@ pub const imports = @import("imports.zig"); pub const imports_tests = @import("imports.test.zig"); pub const core = @import("core.zig"); pub const c_import = @import("c_import.zig"); +pub const c_import_tests = @import("c_import.test.zig"); pub const ir = @import("ir/ir.zig"); pub const lsp = struct {