-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathbuild.zig
More file actions
302 lines (275 loc) · 11.9 KB
/
Copy pathbuild.zig
File metadata and controls
302 lines (275 loc) · 11.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
// Main library module
const eth_module = b.addModule("eth", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
addXkcp(b, eth_module, target);
addSecp256k1(b, eth_module);
addKzg(b, eth_module);
// Unit tests. Root the test artifact at src/root.zig so its test block
// (which direct-imports every module file) actually collects and runs the
// per-module `test` blocks. Aggregating via `_ = eth.module` field access
// only forces semantic analysis -- it does NOT collect those tests, so the
// previous tests-aggregator-rooted artifact compiled assertions without
// ever executing them. The C backends (XKCP, secp256k1) must be re-attached
// because this is a fresh module, not the published `eth` module.
const unit_test_module = b.createModule(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = optimize,
.link_libc = true,
});
addXkcp(b, unit_test_module, target);
addSecp256k1(b, unit_test_module);
addKzg(b, unit_test_module);
const unit_tests = b.addTest(.{
.root_module = unit_test_module,
});
const run_unit_tests = b.addRunArtifact(unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_unit_tests.step);
// Install the unit-test binary so coverage tooling (kcov) can run it
// out-of-band: `zig build install-test` writes it to zig-out/bin/test.
const install_unit_tests = b.addInstallArtifact(unit_tests, .{});
const install_test_step = b.step("install-test", "Install the unit-test binary (for coverage tooling)");
install_test_step.dependOn(&install_unit_tests.step);
// Formatting check as a first-class build step (mirrors the CI fmt job and
// `make fmt`, so `zig build fmt-check` is self-describing without Make).
const fmt_check = b.addFmt(.{
.paths = &.{ "src", "tests", "bench", "build.zig" },
.check = true,
});
const fmt_step = b.step("fmt-check", "Check source formatting (zig fmt --check)");
fmt_step.dependOn(&fmt_check.step);
// API documentation: `zig build docs` emits the HTML docs generated from the
// public surface (src/root.zig) into zig-out/docs.
const docs_obj = b.addObject(.{
.name = "eth",
.root_module = eth_module,
});
const install_docs = b.addInstallDirectory(.{
.source_dir = docs_obj.getEmittedDocs(),
.install_dir = .prefix,
.install_subdir = "docs",
});
const docs_step = b.step("docs", "Generate API documentation into zig-out/docs");
docs_step.dependOn(&install_docs.step);
// Integration tests (requires Anvil)
const integration_tests = b.addTest(.{
.root_module = b.createModule(.{
.root_source_file = b.path("tests/integration_tests.zig"),
.target = target,
.optimize = optimize,
.imports = &.{
.{ .name = "eth", .module = eth_module },
},
}),
});
const run_integration_tests = b.addRunArtifact(integration_tests);
const integration_step = b.step("integration-test", "Run integration tests (requires Anvil)");
integration_step.dependOn(&run_integration_tests.step);
// Benchmarks (always ReleaseFast for meaningful numbers)
const bench_module = b.addModule("eth_bench", .{
.root_source_file = b.path("src/root.zig"),
.target = target,
.optimize = .ReleaseFast,
.link_libc = true,
});
addXkcp(b, bench_module, target);
addSecp256k1(b, bench_module);
addKzg(b, bench_module);
const bench_exe = b.addExecutable(.{
.name = "bench",
.root_module = b.createModule(.{
.root_source_file = b.path("bench/bench.zig"),
.target = target,
.optimize = .ReleaseFast,
.imports = &.{
.{ .name = "eth", .module = bench_module },
},
}),
});
const run_bench = b.addRunArtifact(bench_exe);
const bench_step = b.step("bench", "Run benchmarks (ReleaseFast)");
bench_step.dependOn(&run_bench.step);
// u256-only benchmark (custom harness, no zbench dependency)
const u256_bench_exe = b.addExecutable(.{
.name = "u256-bench",
.root_module = b.createModule(.{
.root_source_file = b.path("bench/u256_bench.zig"),
.target = target,
.optimize = .ReleaseFast,
.imports = &.{
.{ .name = "eth", .module = bench_module },
},
}),
});
const run_u256_bench = b.addRunArtifact(u256_bench_exe);
const u256_bench_step = b.step("bench-u256", "Run u256-only benchmarks (ReleaseFast)");
u256_bench_step.dependOn(&run_u256_bench.step);
// Keccak comparison benchmark (eth.zig vs stdlib)
const keccak_compare_exe = b.addExecutable(.{
.name = "keccak-compare",
.root_module = b.createModule(.{
.root_source_file = b.path("bench/keccak_compare.zig"),
.target = target,
.optimize = .ReleaseFast,
.imports = &.{
.{ .name = "eth", .module = bench_module },
},
}),
});
const run_keccak_compare = b.addRunArtifact(keccak_compare_exe);
const keccak_compare_step = b.step("bench-keccak", "Compare eth.zig Keccak vs stdlib (ReleaseFast)");
keccak_compare_step.dependOn(&run_keccak_compare.step);
// Keccak CLI benchmark (for hyperfine comparison)
const keccak_bench_exe = b.addExecutable(.{
.name = "keccak-bench-zig",
.root_module = b.createModule(.{
.root_source_file = b.path("bench/keccak_bench_cli.zig"),
.target = target,
.optimize = .ReleaseFast,
.imports = &.{
.{ .name = "eth", .module = bench_module },
},
}),
});
b.installArtifact(keccak_bench_exe);
}
/// Add XKCP keccak C sources to a module with CPU-appropriate backend selection.
fn addXkcp(b: *std.Build, module: *std.Build.Module, target: std.Build.ResolvedTarget) void {
// -fno-sanitize=undefined: the XKCP permutation does intentional unaligned
// 64-bit loads (portable optimized C). In Debug builds Zig links the C UBSan
// runtime, which traps those well-defined accesses; disable it for this
// vendored third-party code. Production/benchmarks use ReleaseFast where
// UBSan is off anyway.
const c_flags = &.{ "-O3", "-fno-sanitize=undefined" };
// Common include paths
module.addIncludePath(b.path("src/crypto/xkcp/common"));
module.addIncludePath(b.path("src/crypto/xkcp/high"));
// Select backend based on target CPU
const arch = target.result.cpu.arch;
if (arch == .aarch64) {
// XKCP's ARMv8A NEON assembly uses GAS syntax incompatible with clang's
// integrated assembler. Use the optimized generic 64-bit C backend instead,
// which LLVM will optimize with NEON auto-vectorization.
module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
module.addCSourceFile(.{
.file = b.path("src/crypto/xkcp/plain64/KeccakP-1600-opt64.c"),
.flags = c_flags,
});
} else if (arch == .x86_64) {
const features = target.result.cpu.features;
const avx512f = @intFromEnum(std.Target.x86.Feature.avx512f);
const avx2 = @intFromEnum(std.Target.x86.Feature.avx2);
if (features.isEnabled(avx512f)) {
// AVX512 SnP header must come before plain64 to shadow KeccakP-1600-SnP.h
module.addIncludePath(b.path("src/crypto/xkcp/avx512"));
module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
module.addAssemblyFile(b.path("src/crypto/xkcp/avx512/KeccakP-1600-AVX512.s"));
} else if (features.isEnabled(avx2)) {
// AVX2 SnP header must come before plain64 to shadow KeccakP-1600-SnP.h
module.addIncludePath(b.path("src/crypto/xkcp/avx2"));
module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
module.addAssemblyFile(b.path("src/crypto/xkcp/avx2/KeccakP-1600-AVX2.s"));
} else {
module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
module.addCSourceFile(.{
.file = b.path("src/crypto/xkcp/plain64/KeccakP-1600-opt64.c"),
.flags = c_flags,
});
}
} else {
// Generic 64-bit fallback
module.addIncludePath(b.path("src/crypto/xkcp/plain64"));
module.addCSourceFile(.{
.file = b.path("src/crypto/xkcp/plain64/KeccakP-1600-opt64.c"),
.flags = c_flags,
});
}
// High-level API (sponge + hash)
module.addCSourceFile(.{
.file = b.path("src/crypto/xkcp/high/KeccakSponge.c"),
.flags = c_flags,
});
module.addCSourceFile(.{
.file = b.path("src/crypto/xkcp/high/KeccakHash.c"),
.flags = c_flags,
});
}
/// Add libsecp256k1 (Bitcoin Core) C sources for high-performance EC operations.
fn addSecp256k1(b: *std.Build, module: *std.Build.Module) void {
const c_flags = &.{
"-DENABLE_MODULE_RECOVERY=1", // ecrecover -- essential for Ethereum
"-DENABLE_MODULE_ECDH=1",
"-DENABLE_MODULE_EXTRAKEYS=1",
"-DENABLE_MODULE_SCHNORRSIG=1",
"-DENABLE_MODULE_ELLSWIFT=1",
"-DECMULT_WINDOW_SIZE=15",
"-DCOMB_BLOCKS=43",
"-DCOMB_TEETH=6",
"-O2",
"-Wno-unused-function",
// Vendored audited C with intentional unaligned access; see addXkcp.
"-fno-sanitize=undefined",
};
module.addIncludePath(b.path("src/crypto/secp256k1/include"));
module.addIncludePath(b.path("src/crypto/secp256k1/src"));
// Unity build: 3 C files
const src_files = [_][]const u8{
"src/crypto/secp256k1/src/secp256k1.c",
"src/crypto/secp256k1/src/precomputed_ecmult.c",
"src/crypto/secp256k1/src/precomputed_ecmult_gen.c",
};
for (src_files) |src| {
module.addCSourceFile(.{
.file = b.path(src),
.flags = c_flags,
});
}
}
/// Add c-kzg-4844 + blst C sources for real EIP-4844 KZG operations.
///
/// blst is built in portable no-assembly C mode (`-D__BLST_NO_ASM__`) so it
/// uses its pure-C field arithmetic instead of per-arch assembly. This keeps
/// the build robust across targets (no GAS-vs-clang assembler conflicts) at a
/// modest performance cost, acceptable for sidecar construction. See
/// src/crypto/c-kzg/VENDOR.md for pinned versions and rationale.
fn addKzg(b: *std.Build, module: *std.Build.Module) void {
// blst: portable C backend. `__BLST_PORTABLE__` additionally disables the
// optional SHA CPU-intrinsics path in src/sha256.h. `-fno-sanitize=undefined`
// mirrors the other vendored C: blst does intentional unaligned accesses.
const blst_flags = &.{
"-D__BLST_NO_ASM__",
"-D__BLST_PORTABLE__",
"-O2",
"-fno-sanitize=undefined",
"-Wno-unused-function",
};
module.addIncludePath(b.path("src/crypto/blst/bindings"));
// server.c is blst's unity build: it #includes every other src/*.c.
module.addCSourceFile(.{
.file = b.path("src/crypto/blst/src/server.c"),
.flags = blst_flags,
});
// c-kzg-4844: ckzg.c is the unity build including every other c-kzg .c file.
// Needs its own src/ on the include path (so "common/...", "eip4844/..."
// resolve) plus blst's bindings for "blst.h".
const ckzg_flags = &.{
"-O2",
"-fno-sanitize=undefined",
"-Wno-unused-function",
};
module.addIncludePath(b.path("src/crypto/c-kzg/src"));
module.addIncludePath(b.path("src/crypto/blst/bindings"));
module.addCSourceFile(.{
.file = b.path("src/crypto/c-kzg/src/ckzg.c"),
.flags = ckzg_flags,
});
}