名称: zig
描述: “当请求涉及Zig代码或工具链工作时使用:编辑.zig文件,修改build.zig/build.zig.zon,运行或修复zig build|test|run|fmt|fetch命令,调试编译/运行时/测试失败,编译时/反射/代码生成,分配器所有权,SIMD(std.simd/@Vector),线程(std.Thread/Thread.Pool),交叉编译,零拷贝解析,或C互操作(@cImport)。通过测试、std.testing.fuzz和分配失败检查强制执行正确性优先的验证。”
Zig
何时使用
- 编辑
.zig文件。 - 修改
build.zig或build.zig.zon。 - Zig构建/测试、依赖项、交叉编译。
- 任何Zig工作需要模糊测试(覆盖引导或模糊风格)。
- 性能调优:SIMD(
std.simd/@Vector)和线程(std.Thread.Pool)。 - 编译时、反射、代码生成。
- 分配器、所有权、零拷贝解析。
- C互操作。
基线要求
- Zig 0.15.2。
- 默认集成模糊器:
std.testing.fuzz+zig build test --fuzz。 - 除非明确请求,否则不支持旧版Zig兼容性工作。
快速开始
# 工具链(必需)
zig version # 必须为0.15.2
# 初始化(创建build.zig + src/main.zig)
zig init
# 或(更小模板)
zig init --minimal
# 注意:--minimal不添加`test`构建步骤;除非在build.zig中添加测试步骤,否则`zig build test`/`--fuzz`将失败。
# 格式化
zig fmt src/main.zig
# 构建/运行/测试(build.zig存在)
zig build
zig build run
zig build test
# 模糊测试(集成模糊器)
# 需要在build.zig中有`test`步骤(--minimal模板中没有)。
zig build test --fuzz
# 单文件测试/运行
zig test src/main.zig
zig run src/main.zig
# 触发审计(通过seq的会话级代理)
python3 codex/skills/zig/scripts/zig_trigger_audit.py --root ~/.codex/sessions
python3 codex/skills/zig/scripts/zig_trigger_audit.py --root ~/.codex/sessions --since 2026-02-06T00:00:00Z
工作流程(正确性 -> 速度)
- 声明契约:输入域、输出、不变量、错误模型、复杂度目标。
- 构建参考实现(简单 > 快速)并保留在树中以进行差异比较。
- 单元测试:边缘情况 + 回归。
- 差异模糊:在Debug/ReleaseSafe中比较优化与参考版本。
- 按顺序优化:算法 -> 数据布局 -> SIMD -> 线程 -> 微调。
- 每次优化后重新运行模糊/测试;在ReleaseFast中单独进行基准测试。
正确性要求(非可协商)
- 每个Zig更改至少获得一个正确性信号。
- 对于解析/算术/内存/安全敏感代码,该信号为模糊测试。
- 优先差异模糊(优化与参考),以便证明行为而非推断。
- 默认工具链:
std.testing.fuzz+zig build test --fuzz(Zig 0.15.2基线)。 - 时间无关:没有规定的模糊持续时间;尽可能长时间运行并始终保留发现。
- 在
Debug/ReleaseSafe中运行模糊,以便安全检查保持开启;在ReleaseFast中单独进行基准测试。 - 使用分配器的代码也运行
std.testing.checkAllAllocationFailures。 - 如果模糊测试无法在本地运行(例如,macOS上的
InvalidElfMagic崩溃),说明原因并添加后续步骤(种子语料库 + 重现测试);在Linux/CI或外部工具链中运行模糊。
性能快速开始(主机CPU)
# 用于本地基准测试的高性能构建
zig build-exe -O ReleaseFast -mcpu=native -fstrip src/main.zig
# 发出汇编/优化IR以供检查
zig build-exe -O ReleaseFast -mcpu=native -femit-asm src/main.zig
zig build-exe -O ReleaseFast -mcpu=native -femit-llvm-ir src/main.zig # 需要LLVM扩展
# Build.zig项目(使用b.standardTargetOptions / standardOptimizeOption时)
zig build -Doptimize=ReleaseFast -Dtarget=native -Dcpu=native
常用命令
# 发布版本
zig build -Doptimize=ReleaseFast
# 发布 + LTO(需要LLVM扩展)
zig build-exe -O ReleaseFast -mcpu=native -flto -fstrip src/main.zig
# 交叉编译
zig build -Dtarget=x86_64-linux
zig build -Dtarget=aarch64-macos
# 清理产物
rm -rf zig-out zig-cache
优化立场(针对生成代码)
- 优先算法优势;然后是数据布局;然后是SIMD;然后是线程;然后是微调。
- 保持热循环无分配;将分配视为内核中的正确性异味。
- 优先连续切片和SoA布局;避免热路径中的指针追逐。
- 避免虚假共享:使每线程输出缓存行分离(例如
align(std.atomic.cache_line))。 - 帮助优化器:无分支向量循环、
@branchHint(.likely/.unlikely)和简单控制流。 - 保持快速路径可移植:
std.simd.suggestVectorLength(T)+ 标量后备;线程池使用在builtin.single_threaded上已经降级。
SIMD / 向量化指南
原则:
- 当需要保证SIMD时使用显式向量(
@Vector);仅依赖自动向量化作为额外优势。 - 从
std.simd.suggestVectorLength(T)派生车道数量,以便相同代码在不同目标上扩展。 - 保持向量循环直线:无函数指针、无复杂分支、无隐藏分配。
- 使用标量循环处理尾部(剩余元素)。
- 在某些目标上对齐很重要(特别是ARM);调优时,考虑使用标量序言直到对齐到块大小。
SIMD模板:减少切片
const std = @import("std");
pub fn sumF32(xs: []const f32) f32 {
if (xs.len == 0) return 0;
if (!@inComptime()) if (std.simd.suggestVectorLength(f32)) |lanes| {
const V = @Vector(lanes, f32);
var i: usize = 0;
var acc: V = @splat(0);
while (i + lanes <= xs.len) : (i += lanes) {
const v: V = xs[i..][0..lanes].*;
acc += v;
}
var total: f32 = @reduce(.Add, acc);
while (i < xs.len) : (i += 1) total += xs[i];
return total;
}
var total: f32 = 0;
for (xs) |x| total += x;
return total;
}
SIMD扫描模式(掩码 + 减少)
- 将向量与标量掩码比较:
matches = block == @as(Block, @splat(value))。 - 检测任何匹配:
if (@reduce(.Or, matches)) { ... }。 - 查找第一个匹配索引:
std.simd.firstTrue(matches).?。
SIMD分隔符位掩码模式(CSV类扫描器)
当需要块中所有匹配位置(分隔符、引号、CR、LF)时使用位掩码,而不仅仅是第一个命中。
const std = @import("std");
fn delimMask(block: @Vector(16, u8), delim: u8, quote: u8, cr: u8, lf: u8) u16 {
const matches: @Vector(16, bool) =
(block == @as(@Vector(16, u8), @splat(delim))) or
(block == @as(@Vector(16, u8), @splat(quote))) or
(block == @as(@Vector(16, u8), @splat(cr))) or
(block == @as(@Vector(16, u8), @splat(lf)));
return @bitCast(matches);
}
fn walkMatches(mask0: u16) void {
var mask = mask0;
while (mask != 0) {
const idx = @ctz(mask); // 基于0的车道索引
_ = idx;
mask &= mask - 1; // 清除最低设置位
}
}
- 用单元测试验证车道映射:
@bitCast后,LSB对应车道/索引0。
循环塑造技巧(stdlib证明)
- 使用
inline for展开短内部循环以减少边界检查(见std.mem.indexOfScalarPos)。 - 使用
std.simd.suggestVectorLength(T)匹配stdlib的首选对齐和向量宽度。 - 在执行任何复杂操作时,使用
!@inComptime()和!std.debug.inValgrind()保护向量路径。
线程 / 并行指南
原则:
- 仅当能摊销调度 + 缓存效应时才线程化(小切片通常损失)。
- 按连续范围分区工作;避免热路径中的共享写入和共享锁。
- 使用线程池(
std.Thread.Pool)+ 等待组(std.Thread.WaitGroup),而不是“每个任务生成一个线程”。 - 使任务粗粒度:约
cpu_count到约8*cpu_count个任务,每个执行SIMD内部循环。 - 在最后减少结果;除非真正需要流式聚合,否则避免原子操作。
线程池模板(数据并行)
const std = @import("std");
const builtin = @import("builtin");
fn sumChunk(xs: []const f32, out: *f32) void {
// 每个任务使用SIMD内核。
out.* = sumF32(xs);
}
pub fn sumParallel(xs: []const f32) !f32 {
if (xs.len == 0) return 0;
// 对于面向吞吐量的程序,在ReleaseFast中优先使用std.heap.smp_allocator。
// 当使用-fsingle-threaded编译时,smp_allocator不可用。
const alloc = if (builtin.single_threaded) std.heap.page_allocator else std.heap.smp_allocator;
var pool: std.Thread.Pool = undefined;
try pool.init(.{ .allocator = alloc });
defer pool.deinit();
const cpu_count = @max(1, std.Thread.getCpuCount() catch 1);
const task_count = @min(cpu_count * 4, xs.len);
const chunk_len = (xs.len + task_count - 1) / task_count;
var partials = try alloc.alloc(f32, task_count);
defer alloc.free(partials);
var wg: std.Thread.WaitGroup = .{};
for (0..task_count) |t| {
const start = t * chunk_len;
const end = @min(xs.len, start + chunk_len);
pool.spawnWg(&wg, sumChunk, .{ xs[start..end], &partials[t] });
}
// 让调用线程帮助执行队列中的工作。
pool.waitAndWork(&wg);
var total: f32 = 0;
for (partials) |p| total += p;
return total;
}
每线程暂存(无分配器争用)
- 使用
.track_ids = true初始化池。 - 使用
pool.spawnWgId(&wg, func, args);func首先接收id: usize。 - 保持
scratch[id]对齐到std.atomic.cache_line以防止虚假共享。
const std = @import("std");
const Scratch = struct {
_: void align(std.atomic.cache_line) = {},
tmp: [4096]u8 = undefined,
};
fn work(id: usize, input: []const u8, scratch: []Scratch) void {
// 稳定的每线程槽;无锁,无虚假共享。
const buf = scratch[id].tmp[0..];
_ = buf;
_ = input;
}
pub fn runParallel(input: []const u8, allocator: std.mem.Allocator) !void {
var pool: std.Thread.Pool = undefined;
try pool.init(.{ .allocator = allocator, .track_ids = true });
defer pool.deinit();
const scratch = try allocator.alloc(Scratch, pool.getIdCount());
defer allocator.free(scratch);
var wg: std.Thread.WaitGroup = .{};
pool.spawnWgId(&wg, work, .{ input, scratch });
pool.waitAndWork(&wg);
}
编译时元编程(Zig 0.15.2)
原则:
- 使用编译时进行专门化和验证;像测量运行时一样测量编译时间。
- 优先数据而非代码生成;仅当代码生成解锁优化时才生成代码。
- 使用
@compileError在边界处使非法状态不可表示。
核心工具:
- 类型反射:
@typeInfo,@Type,@TypeOf,@typeName。 - 命名空间/字段:
@hasDecl,@field,@FieldType,std.meta.fields,std.meta.declarations。 - 布局 + ABI:
@sizeOf,@alignOf,@bitSizeOf,@offsetOf,@fieldParentPtr。 - 受控展开:
inline for,inline while,comptime if。 - 诊断:
@compileError,@compileLog。 - 成本控制:
@setEvalBranchQuota(局部、有理由),--time-report。
常见模式:
- 特性:在编译时断言所需声明/方法。
- 字段级派生:通过迭代字段生成
eql/hash/format/serialize。 - 静态表:小的
std.StaticStringMap.initComptime;大型枚举优先inline for扫描(见std.meta.stringToEnum)。 - 内核工厂:
fn Kernel(comptime lanes: usize, comptime unroll: usize) type { ... }+std.simd.suggestVectorLength。
不公平工具箱(stdlib证明):
std.meta.eql:容器的深度相等性(指针不跟随)。std.meta.hasUniqueRepresentation:控制“memcmp风格”快速路径。std.meta.FieldEnum/std.meta.DeclEnum:将字段/声明转换为枚举以便于开关。std.meta.Tag/std.meta.activeTag:读取标记联合的标签。std.meta.fields/std.meta.declarations:无需原始@typeInfo管道的一行反射。
特性检查模板
const std = @import("std");
fn assertHasRead(comptime T: type) void {
if (!std.meta.hasMethod(T, "read")) {
@compileError(@typeName(T) ++ " 必须实现 read()");
}
}
字段级派生模板(结构体)
const std = @import("std");
fn eqlStruct(a: anytype, b: @TypeOf(a)) bool {
const T = @TypeOf(a);
if (@typeInfo(T) != .@"struct") @compileError("eqlStruct 期望结构体");
inline for (std.meta.fields(T)) |f| {
if (!std.meta.eql(@field(a, f.name), @field(b, f.name))) return false;
}
return true;
}
布局断言(ABI锁定)
comptime {
const Header = extern struct {
magic: u32,
version: u16,
flags: u16,
len: u32,
_pad: u32,
};
if (@sizeOf(Header) != 16) @compileError("Header ABI: 大小");
if (@alignOf(Header) != 4) @compileError("Header ABI: 对齐");
if (@offsetOf(Header, "magic") != 0) @compileError("Header ABI: magic 偏移量");
if (@offsetOf(Header, "len") != 8) @compileError("Header ABI: len 偏移量");
}
联合访问者(内联开关)
fn visit(u: anytype) void {
switch (u) {
inline else => |payload, tag| {
_ = payload;
_ = tag; // 编译时已知标签
},
}
}
类型形状调度器(派生任何内容)
使用此功能编写一个支持结构体/联合/枚举/指针/数组等的派生管道。保持返回类型统一(R),以便调用站点保持简单。
实现 + 测试:codex/skills/zig/references/type_switch.zig。
验证:zig test codex/skills/zig/references/type_switch.zig
@Type 构建器(精确)
当真正需要从输入类型制造新类型时,使用@Type。这很锐利:当std.meta.*能表达相同意图时,优先使用std.meta.*。
示例:构建一个“补丁”类型,其中每个运行时字段为?T,默认为null。
实现 + 测试:codex/skills/zig/references/partial_type.zig。
验证:zig test codex/skills/zig/references/partial_type.zig
派生管道(遍历 + 策略;真正不公平)
一次遍历发出语义事件;策略决定做什么(哈希、格式化、序列化、统计)。遍历拥有排序 + 预算;策略拥有语义。
实现 + 测试:codex/skills/zig/references/derive_walk_policy.zig。
验证:zig test codex/skills/zig/references/derive_walk_policy.zig
注意:格式化辅助函数接受写入器指针(例如&w),以便保留状态。
当表示唯一时的快速路径
const std = @import("std");
fn eqlFast(a: anytype, b: @TypeOf(a)) bool {
const T = @TypeOf(a);
if (comptime std.meta.hasUniqueRepresentation(T)) {
return std.mem.eql(u8, std.mem.asBytes(&a), std.mem.asBytes(&b));
}
return std.meta.eql(a, b);
}
编译时成本护栏
- 避免组合专门化:保持旋钮表面小而明确。
- 避免用于大型域的庞大编译时映射;优先
inline for扫描。 - 如果必须提高分支配额,请在需要的最小子循环中执行。
- 优先
std.meta.*辅助函数而非手写@typeInfo管道(代码更少、错误更少)。
编译时示例
const std = @import("std");
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
test "编译时参数" {
const x = max(u32, 3, 5);
try std.testing.expect(x == 5);
}
编译时性能
模式:
- 在车道计数和展开因子上专门化(
comptime lanes,comptime unroll)。 - 在编译时生成查找表(例如,分类映射、洗牌索引)。
- 当需要不同内核时,优先
comptime if进行CPU/架构调度(builtin.cpu.arch)。
编译时专门化示例(SIMD点积)
const std = @import("std");
fn Dot(comptime lanes: usize) type {
return struct {
pub fn dot(a: []const f32, b: []const f32) f32 {
const V = @Vector(lanes, f32);
var i: usize = 0;
var acc: V = @splat(0);
while (i + lanes <= a.len) : (i += lanes) {
const av: V = a[i..][0..lanes].*;
const bv: V = b[i..][0..lanes].*;
acc += av * bv;
}
var total: f32 = @reduce(.Add, acc);
while (i < a.len) : (i += 1) total += a[i] * b[i];
return total;
}
};
}
pub fn dotAuto(a: []const f32, b: []const f32) f32 {
const lanes = std.simd.suggestVectorLength(f32) orelse 1;
return Dot(lanes).dot(a, b);
}
构建要点(build.zig)
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "my-app",
.root_module = b.createModule(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
}),
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
const run_step = b.step("run", "运行应用");
run_step.dependOn(&run_cmd.step);
if (b.args) |args| run_cmd.addArgs(args);
}
包管理(build.zig.zon)
.{
.name = "my-project",
.version = "0.1.0",
.dependencies = .{
.@"some-package" = .{
.url = "https://github.com/user/package/archive/main.tar.gz",
.hash = "1220abcdef...",
},
},
.paths = .{ "build.zig", "build.zig.zon", "src" },
}
内存 / 分配器(性能优先)
经验法则:
- 调试正确性/泄漏:在测试中使用
std.testing.allocator,或在应用中使用std.heap.DebugAllocator。 - 吞吐量 + 多线程(ReleaseFast):
std.heap.smp_allocator(单例,为MT + ReleaseFast设计)。 - 短期“构建结果然后丢弃”:在快速后备分配器上使用
std.heap.ArenaAllocator。 - 暂存缓冲区:
std.heap.FixedBufferAllocator或std.heap.stackFallback(N, fallback)。 - 固定大小对象:
std.heap.MemoryPool/std.heap.MemoryPoolAligned。
调试分配器(泄漏检查)
const std = @import("std");
pub fn main() !void {
var dbg = std.heap.DebugAllocator(.{}){};
defer _ = dbg.deinit();
const allocator = dbg.allocator();
const bytes = try allocator.alloc(u8, 100);
defer allocator.free(bytes);
}
Smp分配器 + 竞技场重置(热循环友好)
const std = @import("std");
const builtin = @import("builtin");
pub fn buildManyThings() !void {
const backing = if (builtin.single_threaded) std.heap.page_allocator else std.heap.smp_allocator;
var arena = std.heap.ArenaAllocator.init(backing);
defer arena.deinit();
const a = arena.allocator();
var i: usize = 0;
while (i < 1000) : (i += 1) {
_ = arena.reset(.retain_capacity);
_ = try a.alloc(u8, 4096);
}
}
检查代码生成 / 基准测试
- 发出汇编:
zig build-exe -O ReleaseFast -mcpu=native -femit-asm src/main.zig - 发出优化LLVM IR:
zig build-exe -O ReleaseFast -mcpu=native -femit-llvm-ir src/main.zig(LLVM扩展) - 跟踪编译时间:
--time-report - 防止基准测试中的DCE:
std.mem.doNotOptimizeAway(x) - 时间循环:
std.time.Timer,std.time.nanoTimestamp
解析工作的基准测试契约
- 仅基准测试解析/迭代循环;排除文件下载/解压缩和设置噪声。
- 在报告中保持缓冲区大小固定(例如64 KiB),以便运行可比较。
- 报告墙时间和至少一组硬件计数器(分支失误、缓存失误)加上RSS。
- 在基准测试说明中捕获机器 + 操作系统 + 编译器版本 + 数据集混合。
零拷贝解析指南
原则:
- 将输入视为不可变字节;解析为视图而非副本。
- 使所有权明确(借用 vs 拥有)。
- 将跨度/偏移量存储到稳定的基缓冲区中。
- 永远不返回指向临时缓冲区的切片。
迭代器生命周期 + 流式缓冲区契约(CSV类)
- 对于热路径,优先字段迭代器(每个
next()一个字段),然后在顶部构建记录视图。 - 明确文档化生命周期:返回的字段切片在下一次
next()调用之前有效,除非它们引用调用者拥有的稳定后备存储。 - 对于流式输入,在重新填充之前将部分令牌移到前端(
@memmove),然后继续。 - 如果令牌超过缓冲区容量,使用明确错误快速失败(
error.FieldTooLong),而不是在热循环中截断或重新分配。
fn refillKeepingTail(buf: []u8, head: *usize, tail: *usize, reader: anytype) !usize {
if (head.* > 0 and head.* < tail.*) {
const rem = tail.* - head.*;
@memmove(buf[0..rem], buf[head.*..tail.*]);
head.* = 0;
tail.* = rem;
} else if (head.* == tail.*) {
head.* = 0;
tail.* = 0;
}
if (tail.* == buf.len) return error.FieldTooLong;
const n = try reader.read(buf[tail.*..]);
tail.* += n;
return n;
}
引号字段有限状态路径(稳健CSV)
- 早期分为非引号和引号路径;保持非引号路径分支轻量。
- 在引号模式下,将
""视为转义引号,然后需要在关闭引号后是分隔符/换行/输入结束。 - 在扫描期间跟踪
needs_unescape,仅在必要时取消转义。 - 在热循环中使用整数布尔值标准化CRLF以避免额外分支。
const has_lf: usize = @intFromBool(end > start and buf[end - 1] == '
');
const has_cr: usize = @intFromBool(end > start + has_lf and buf[end - 1 - has_lf] == '\r');
const trimmed_end = end - has_lf - has_cr;
借用/拥有令牌(按需复制的逃生口)
const std = @import("std");
pub const ByteView = union(enum) {
borrowed: []const u8,
owned: []u8,
pub fn slice(self: ByteView) []const u8 {
return switch (self) {
.borrowed => |s| s,
.owned => |s| s,
};
}
pub fn toOwned(self: ByteView, allocator: std.mem.Allocator) ![]u8 {
return switch (self) {
.owned => |s| s,
.borrowed => |s| try allocator.dupe(u8, s),
};
}
pub fn deinit(self: *ByteView, allocator: std.mem.Allocator) void {
if (self.* == .owned) allocator.free(self.owned);
self.* = .{ .borrowed = &.{} };
}
};
POSIX mmap(稳定基缓冲区)
const std = @import("std");
pub const MappedFile = struct {
data: []const u8,
owns: bool,
pub fn open(path: []const u8) !MappedFile {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const size = (try file.stat()).size;
const map = try std.posix.mmap(
null,
size,
std.posix.PROT.READ,
.{ .TYPE = .PRIVATE },
file.handle,
0,
);
return .{ .data = map, .owns = true };
}
pub fn close(self: *MappedFile) void {
if (self.owns) std.posix.munmap(self.data);
self.* = .{ .data = &.{}, .owns = false };
}
};
基于跨度的解析(偏移量,非副本)
const Span = struct {
base: []const u8,
start: usize,
len: usize,
pub fn slice(self: Span) []const u8 {
return self.base[self.start..][0..self.len];
}
};
测试
- 在Debug或ReleaseSafe中运行正确性测试;在ReleaseFast中运行性能检查。
- 泄漏检测:使用
std.testing.allocator和defer释放。 - 优先差异测试(参考 vs 优化)和变形不变量(往返、单调性)。
- 分配计数:包装分配器并断言“零拷贝”路径的零分配。
- OOM注入:在
std.testing.FailingAllocator下运行。 - 彻底OOM:
std.testing.checkAllAllocationFailures。
模糊测试(必需)
内置模糊器(默认,Zig 0.15.2)
在test块中使用std.testing.fuzz并运行:
zig build test --fuzz(可选-Doptimize=ReleaseSafe)。
参考zig build test --help获取版本特定的--fuzz标志。
macOS 注意事项(Zig 0.15.2)
首先在macOS上尝试zig build test --fuzz。如果在启动期间崩溃(观察到InvalidElfMagic),跳过该运行的本地模糊测试,保持std.testing.fuzz测试在树中,并在Linux/CI或通过外部工具链中运行模糊步骤;使用zig test本地进行烟雾覆盖。
模糊目标规则(使其模糊器友好)
- 确定性:无计时器、线程或内部RNG(模糊器是RNG)。
- 全面:接受任何输入字节;绝不读取越界;无基于假设的UB。
- 有界:限制病态工作(长度限制、递归深度、最大分配)。
- 隔离:无全局可变状态(或每次调用完全重置)。
- 断言属性,而非感觉:参考等价、往返、单调性、不变量。
差异模糊(推荐)
使模糊目标断言小型参考实现与优化内核之间的等价性。这是实现算法正确性的最快途径。
编译检查模板:codex/skills/zig/references/fuzz_differential.zig。
模板:
const std = @import("std");
fn refCountOnes(bytes: []const u8) u64 {
var n: u64 = 0;
for (bytes) |b| n += @popCount(b);
return n;
}
fn fastCountOnes(bytes: []const u8) u64 {
// 替换为优化版本(SIMD/线程等)。
return refCountOnes(bytes);
}
fn fuzzTarget(_: void, input: []const u8) !void {
const ref = refCountOnes(input);
const got = fastCountOnes(input);
try std.testing.expectEqual(ref, got);
}
test "模糊目标" {
try std.testing.fuzz({}, fuzzTarget, .{});
}
分配失败模糊(分配器必需)
std.testing.checkAllAllocationFailures彻底注入error.OutOfMemory跨越测试函数中的所有分配。测试函数必须将分配器作为其第一个参数,返回!void,并每次运行重置共享状态。
const std = @import("std");
fn parseWithAlloc(alloc: std.mem.Allocator, bytes: []const u8) !void {
_ = alloc;
_ = bytes;
}
test "分配失败模糊" {
const input = "seed";
try std.testing.checkAllAllocationFailures(
std.testing.allocator,
parseWithAlloc,
.{input},
);
}
分配器压力技巧(推荐)
- 相对于输入长度限制每次分配大小,以暴露病态分配。
- 用
std.testing.FailingAllocator包装以验证errdefer和清理路径。 - 在
testdata/fuzz/下保存有趣输入,并将崩溃提升为回归测试。
语料库 + 回归工作流(必需)
- 当模糊发现崩溃/不匹配时,将输入保存在
testdata/fuzz/<目标>/下。 - 使用
@embedFile添加确定性回归测试。
test "回归: 模糊崩溃" {
const input = @embedFile("testdata/fuzz/parser/crash-<id>.bin");
try std.testing.expectEqual(refCountOnes(input), fastCountOnes(input));
}
快速树内随机模糊(烟雾;补充--fuzz)
在test块内使用随机输入进行廉价、始终开启的覆盖。
const std = @import("std");
fn parse(bytes: []const u8) !void {
_ = bytes;
}
test "模糊解析" {
var prng = std.rand.DefaultPrng.init(0x9e3779b97f4a7c15);
const rng = prng.random();
var buf: [4096]u8 = undefined;
var i: usize = 0;
while (i < 10_000) : (i += 1) {
const len = rng.intRangeAtMost(usize, 0, buf.len);
const input = buf[0..len];
rng.bytes(input);
_ = parse(input) catch {};
}
}
外部工具链(可选)
如果需要AFL++/libFuzzer基础设施(共享语料库、分布式模糊、自定义检测),导出C ABI入口点并从外部工具链驱动。 示例大纲:
- 导出稳定入口点:
export fn fuzz_target(ptr: [*]const u8, len: usize) void。 - 使用
zig build-lib构建静态库。 - 从外部工具链(通过
cargo-afl的AFL++)链接它,并使用种子语料库运行。
C 互操作
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() void {
_ = c.printf("Hello from C!
");
}
陷阱
- 多线程:虚假共享、过度订阅、共享分配器争用。
- SIMD:某些目标上的未对齐加载、读取越界、非关联FP减少。
- SIMD位掩码:当将
@Vector(N, bool)@bitCast为整数掩码时,位0对应车道/索引0。 std.heap.GeneralPurposeAllocator已弃用(DebugAllocator的别名);为现有代码保留,为新代码优先显式分配器选择。- 使所有权明确;始终释放堆分配。
- 避免返回基于栈内存的切片。
[*c]T可空;[*]T非空。- 使用
zig fetch --save填充build.zig.zon哈希。
激活提示
- “zig” / “ziglang” / “.zig”
- “build.zig” / “build.zig.zon”
- “zig build” / “zig test”
- “comptime” / “allocator” / “@typeInfo” / “@compileError”
- “SIMD” / “@Vector” / “std.simd”
- “thread” / “std.Thread” / “Thread.Pool” / “WaitGroup”