1//! This file is shared among Zig code running in wildly different contexts:
2//! * The build runner, running on the host computer
3//! * The build system web interface Wasm code, running in the browser
4//! * `libfuzzer`, compiled alongside unit tests
5//!
6//! All of these components interface to some degree via an ABI:
7//! * The build runner communicates with the web interface over a WebSocket connection
8//! * The build runner communicates with `libfuzzer` over a shared memory-mapped file
9const std = @import("std");
10
11// Check that no WebSocket message type has implicit padding bits. This ensures we never send any
12// undefined bits over the wire, and also helps validate that the layout doesn't differ between, for
13// instance, the web server in `std.Build` and the Wasm client.
14comptime {
15 const check = struct {
16 fn check(comptime T: type) void {
17 std.debug.assert(@typeInfo(T) == .@"struct");
18 std.debug.assert(@typeInfo(T).@"struct".layout == .@"extern");
19 std.debug.assert(std.meta.hasUniqueRepresentation(T));
20 }
21 }.check;
22
23 // server->client
24 check(Hello);
25 check(StatusUpdate);
26 check(StepUpdate);
27 check(fuzz.SourceIndexHeader);
28 check(fuzz.CoverageUpdateHeader);
29 check(fuzz.EntryPointHeader);
30 check(time_report.GenericResult);
31 check(time_report.CompileResult);
32
33 // client->server
34 check(Rebuild);
35}
36
37/// All WebSocket messages sent by the server to the client begin with a `ToClientTag` byte. This
38/// enum is non-exhaustive only to avoid Illegal Behavior when malformed messages are sent over the
39/// socket; unnamed tags are an error condition and should terminate the connection.
40///
41/// Every tag has a curresponding `extern struct` representing the full message (or a header of the
42/// message if it is variable-length). For instance, `.hello` corresponds to `Hello`.
43///
44/// When introducing a tag, make sure to add a corresponding `extern struct` whose first field is
45/// this enum, and `check` its layout in the `comptime` block above.
46pub const ToClientTag = enum(u8) {
47 hello,
48 status_update,
49 step_update,
50
51 // `--fuzz`
52 fuzz_source_index,
53 fuzz_coverage_update,
54 fuzz_entry_points,
55
56 // `--time-report`
57 time_report_generic_result,
58 time_report_compile_result,
59 time_report_run_test_result,
60
61 _,
62};
63
64/// Like `ToClientTag`, but for messages sent by the client to the server.
65pub const ToServerTag = enum(u8) {
66 rebuild,
67
68 _,
69};
70
71/// The current overall status of the build runner.
72/// Keep in sync with indices in web UI `main.js:updateBuildStatus`.
73pub const BuildStatus = enum(u8) {
74 idle,
75 watching,
76 running,
77 fuzz_init,
78};
79
80/// WebSocket server->client.
81///
82/// Sent by the server as the first message after a WebSocket connection opens to provide basic
83/// information about the server, the build graph, etc.
84///
85/// Trailing:
86/// * `step_name_len: u32` for each `steps_len`
87/// * `step_name: [step_name_len]u8` for each `step_name_len`
88/// * `step_status: u8` for every 4 `steps_len`; every 2 bits is a `StepUpdate.Status`, LSBs first
89pub const Hello = extern struct {
90 tag: ToClientTag = .hello,
91
92 status: BuildStatus,
93 flags: Flags,
94
95 /// Any message containing a timestamp represents it as a number of nanoseconds relative to when
96 /// the build began. This field is the current timestamp, represented in that form.
97 timestamp: i64 align(4),
98
99 /// The number of steps in the build graph which are reachable from the top-level step[s] being
100 /// run; in other words, the number of steps which will be executed by this build. The name of
101 /// each step trails this message.
102 steps_len: u32 align(1),
103
104 pub const Flags = packed struct(u16) {
105 /// Whether time reporting is enabled.
106 time_report: bool,
107 _: u15 = 0,
108 };
109};
110/// WebSocket server->client.
111///
112/// Indicates that the build status has changed.
113pub const StatusUpdate = extern struct {
114 tag: ToClientTag = .status_update,
115 new: BuildStatus,
116};
117/// WebSocket server->client.
118///
119/// Indicates a change in a step's status.
120pub const StepUpdate = extern struct {
121 tag: ToClientTag = .step_update,
122 step_idx: u32 align(1),
123 bits: packed struct(u8) {
124 status: Status,
125 _: u6 = 0,
126 },
127 /// Keep in sync with indices in web UI `main.js:updateStepStatus`.
128 pub const Status = enum(u2) {
129 pending,
130 wip,
131 success,
132 failure,
133 };
134};
135
136pub const Rebuild = extern struct {
137 tag: ToServerTag = .rebuild,
138};
139
140/// ABI bits specifically relating to the fuzzer interface.
141pub const fuzz = struct {
142 /// Returns if `error.SkipZigTest` was indicated
143 pub const TestOne = *const fn () callconv(.c) bool;
144
145 /// A unique value to identify the related requests across runs
146 pub const Uid = packed struct(u32) {
147 kind: enum(u1) { int, bytes },
148 hash: u31,
149
150 pub const hashmap_ctx = struct {
151 pub fn hash(_: @This(), u: Uid) u32 {
152 // We can ignore `kind` since `hash` should be unique regardless
153 return u.hash;
154 }
155
156 pub fn eql(_: @This(), a: Uid, b: Uid, _: usize) bool {
157 return a == b;
158 }
159 };
160 };
161
162 pub extern fn fuzzer_init(cache_dir_path: Slice) void;
163 /// `fuzzer_init` must be called first.
164 pub extern fn fuzzer_coverage() Coverage;
165 pub extern fn fuzzer_unslide_address(addr: usize) usize;
166
167 /// Performs all the fuzzing work and selects tests to run
168 ///
169 /// `fuzzer_init` must be called first.
170 pub extern fn fuzzer_main(
171 n_tests: u32,
172 seed: u32,
173 limit_kind: LimitKind,
174 amount_or_instance: u64,
175 ) void;
176 pub extern fn runner_test_run(i: u32) void;
177 pub extern fn runner_test_name(i: u32) Slice;
178 // Since the runner owns the `std.zig.Server` instance, it also controls the
179 // concurrent Io instance so reads can be canceled. As such, the fuzzer has
180 // to call into the runner for any zig server / concurrent operation.
181 pub extern fn runner_start_input_poller() void;
182 pub extern fn runner_stop_input_poller() void;
183 /// Returns if cancelation has been indicated.
184 pub extern fn runner_futex_wait(*const u32, expected: u32) bool;
185 pub extern fn runner_futex_wake(*const u32, waiters: u32) void;
186 pub extern fn runner_broadcast_input(test_i: u32, bytes: Slice) void;
187 /// `fuzzer_main` must be called first.
188 ///
189 /// Called concurrently with `fuzzer_main`. Returns if cancelation has been indicated.
190 pub extern fn fuzzer_receive_input(test_i: u32, bytes: Slice) bool;
191
192 /// Must be called from inside a test function
193 pub extern fn fuzzer_set_test(test_one: TestOne) void;
194 /// Must be called from inside a test function where `fuzzer_set_test` has been called first.
195 pub extern fn fuzzer_new_input(bytes: Slice) void;
196 /// Must be called from inside a test function where `fuzzer_set_test` has been called first.
197 pub extern fn fuzzer_start_test() void;
198
199 pub extern fn fuzzer_int(uid: Uid, weights: Weights) u64;
200 pub extern fn fuzzer_eos(uid: Uid, weights: Weights) bool;
201 pub extern fn fuzzer_bytes(uid: Uid, out: MutSlice, weights: Weights) void;
202 pub extern fn fuzzer_slice(
203 uid: Uid,
204 buf: MutSlice,
205 len_weights: Weights,
206 byte_weights: Weights,
207 ) u32;
208
209 pub const Slice = extern struct {
210 ptr: [*]const u8,
211 len: usize,
212
213 pub fn toSlice(s: Slice) []const u8 {
214 return s.ptr[0..s.len];
215 }
216
217 pub fn fromSlice(s: []const u8) Slice {
218 return .{ .ptr = s.ptr, .len = s.len };
219 }
220 };
221
222 pub const MutSlice = extern struct {
223 ptr: [*]u8,
224 len: usize,
225
226 pub fn toSlice(s: MutSlice) []u8 {
227 return s.ptr[0..s.len];
228 }
229
230 pub fn fromSlice(s: []u8) MutSlice {
231 return .{ .ptr = s.ptr, .len = s.len };
232 }
233 };
234
235 pub const Weights = extern struct {
236 ptr: [*]const Weight,
237 len: usize,
238
239 pub fn toSlice(s: Weights) []const Weight {
240 return s.ptr[0..s.len];
241 }
242
243 pub fn fromSlice(s: []const Weight) Weights {
244 return .{ .ptr = s.ptr, .len = s.len };
245 }
246 };
247
248 /// Increases the probability of values being selected by the fuzzer.
249 ///
250 /// `weight` applies to each value in the range (i.e. not evenly across
251 /// the range) and must be nonzero.
252 ///
253 /// In a set of weights, the total weight must not exceed 2^64 and be
254 /// nonzero.
255 pub const Weight = extern struct {
256 /// Inclusive
257 min: u64,
258 /// Inclusive
259 max: u64,
260 weight: u64,
261
262 /// `inline` to propogate comptimeness
263 inline fn intFromValue(x: anytype) u64 {
264 const T = @TypeOf(x);
265 return switch (@typeInfo(T)) {
266 .comptime_int => x,
267 .bool => @intFromBool(x),
268 .@"enum" => @backingInt(x),
269 else => @as(@Int(.unsigned, @bitSizeOf(T)), @bitCast(x)),
270
271 .int => |i| x: {
272 comptime {
273 if (i.signedness == .signed) {
274 @compileError("type does not have a continous range: " ++ @typeName(T));
275 }
276 // Reject types that don't have a fixed bitsize (esp. usize)
277 // since they are not gauraunteed to fit in a u64 across targets.
278 //
279 // std.mem.indexOfScalar is not used to avoid backward branches
280 // and preserve the eval branch quota.
281 if (T == usize or T == c_char or T == c_ushort or
282 T == c_uint or T == c_ulong or T == c_ulonglong)
283 {
284 @compileError("type does not have a fixed bitsize: " ++ @typeName(T));
285 }
286 }
287 break :x x;
288 },
289
290 .comptime_float,
291 .float,
292 => @compileError("type does not have a continous range: " ++ @typeName(T)),
293 .pointer => @compileError("type does not have a fixed bitsize: " ++ @typeName(T)),
294 };
295 }
296
297 /// `inline` to propogate comptimeness
298 pub inline fn value(T: type, x: T, weight: u64) Weight {
299 return .{ .min = intFromValue(x), .max = intFromValue(x), .weight = weight };
300 }
301
302 /// `inline` to propogate comptimeness
303 pub inline fn rangeAtMost(T: type, at_least: T, at_most: T, weight: u64) Weight {
304 std.debug.assert(intFromValue(at_least) <= intFromValue(at_most));
305 return .{
306 .min = intFromValue(at_least),
307 .max = intFromValue(at_most),
308 .weight = weight,
309 };
310 }
311
312 /// `inline` to propogate comptimeness
313 pub inline fn rangeLessThan(T: type, at_least: T, less_than: T, weight: u64) Weight {
314 std.debug.assert(intFromValue(at_least) < intFromValue(less_than));
315 return .{
316 .min = intFromValue(at_least),
317 .max = intFromValue(less_than) - 1,
318 .weight = weight,
319 };
320 }
321 };
322
323 pub const LimitKind = enum(u8) { forever, iterations };
324
325 /// libfuzzer uses this and its usize is the one that counts. To match the ABI,
326 /// make the ints be the size of the target used with libfuzzer.
327 ///
328 /// Trailing:
329 /// * 1 bit per pc_addr, usize elements
330 /// * pc_addr: usize for each pcs_len
331 pub const SeenPcsHeader = extern struct {
332 n_runs: usize,
333 unique_runs: usize,
334 pcs_len: usize,
335
336 /// Used for comptime assertions. Provides a mechanism for strategically
337 /// causing compile errors.
338 pub const trailing = .{
339 .pc_bits_usize,
340 .pc_addr,
341 };
342
343 pub fn headerEnd(header: *const SeenPcsHeader) []const usize {
344 const ptr: [*]align(@alignOf(usize)) const u8 = @ptrCast(header);
345 const header_end_ptr: [*]const usize = @ptrCast(ptr + @sizeOf(SeenPcsHeader));
346 const pcs_len = header.pcs_len;
347 return header_end_ptr[0 .. pcs_len + seenElemsLen(pcs_len)];
348 }
349
350 pub fn seenBits(header: *const SeenPcsHeader) []const usize {
351 return header.headerEnd()[0..seenElemsLen(header.pcs_len)];
352 }
353
354 pub fn seenElemsLen(pcs_len: usize) usize {
355 return (pcs_len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
356 }
357
358 pub fn pcAddrs(header: *const SeenPcsHeader) []const usize {
359 const pcs_len = header.pcs_len;
360 return header.headerEnd()[seenElemsLen(pcs_len)..][0..pcs_len];
361 }
362 };
363
364 /// Fields are little-endian
365 pub const MmapInputHeader = extern struct {
366 pc_digest: u64 align(4), // aligned so header does not have padding
367 instance_id: u32,
368 test_i: u32,
369 len: u32,
370 };
371
372 /// WebSocket server->client.
373 ///
374 /// Sent once, when fuzzing starts, to indicate the available coverage data.
375 ///
376 /// Trailing:
377 /// * std.debug.Coverage.String for each directories_len
378 /// * std.debug.Coverage.File for each files_len
379 /// * std.debug.Coverage.SourceLocation for each source_locations_len
380 /// * u8 for each string_bytes_len
381 pub const SourceIndexHeader = extern struct {
382 tag: ToClientTag = .fuzz_source_index,
383 _: [3]u8 = @splat(0),
384 directories_len: u32,
385 files_len: u32,
386 source_locations_len: u32,
387 string_bytes_len: u32,
388 /// When, according to the server, fuzzing started.
389 start_timestamp: i64 align(4),
390 start_n_runs: u64 align(4),
391 };
392
393 /// WebSocket server->client.
394 ///
395 /// Sent whenever the set of covered source locations is updated.
396 ///
397 /// Trailing:
398 /// * one bit per source_locations_len, contained in u64 elements
399 pub const CoverageUpdateHeader = extern struct {
400 tag: ToClientTag = .fuzz_coverage_update,
401 _: [7]u8 = @splat(0),
402 n_runs: u64,
403 unique_runs: u64,
404
405 pub const trailing = .{
406 .pc_bits_usize,
407 };
408 };
409
410 /// WebSocket server->client.
411 ///
412 /// Sent whenever the set of entry points is updated.
413 ///
414 /// Trailing:
415 /// * one u32 index of source_locations per locsLen()
416 pub const EntryPointHeader = extern struct {
417 tag: ToClientTag = .fuzz_entry_points,
418 locs_len_raw: [3]u8,
419
420 pub fn locsLen(hdr: EntryPointHeader) u24 {
421 return @bitCast(hdr.locs_len_raw);
422 }
423 pub fn init(locs_len: u24) EntryPointHeader {
424 return .{ .locs_len_raw = @bitCast(locs_len) };
425 }
426 };
427
428 /// Sent by lib/fuzzer to test_runner to obtain information about the
429 /// active memory mapped input file and cumulative stats about previous
430 /// fuzzing runs.
431 pub const Coverage = extern struct {
432 id: u64,
433 runs: u64,
434 unique: u64,
435 seen: u64,
436 };
437};
438
439/// ABI bits specifically relating to the time report interface.
440pub const time_report = struct {
441 /// WebSocket server->client.
442 ///
443 /// Sent after a `Step` finishes, providing the time taken to execute the step.
444 pub const GenericResult = extern struct {
445 tag: ToClientTag = .time_report_generic_result,
446 step_idx: u32 align(1),
447 ns_total: u64 align(1),
448 };
449
450 /// WebSocket server->client.
451 ///
452 /// Sent after a `Step.Compile` finishes, providing the step's time report.
453 ///
454 /// Trailing:
455 /// * `llvm_pass_timings: [llvm_pass_timings_len]u8` (ASCII-encoded)
456 /// * for each `files_len`:
457 /// * `name` (null-terminated UTF-8 string)
458 /// * for each `decls_len`:
459 /// * `name` (null-terminated UTF-8 string)
460 /// * `file: u32` (index of file this decl is in)
461 /// * `sema_ns: u64` (nanoseconds spent semantically analyzing this decl)
462 /// * `codegen_ns: u64` (nanoseconds spent semantically analyzing this decl)
463 /// * `link_ns: u64` (nanoseconds spent semantically analyzing this decl)
464 pub const CompileResult = extern struct {
465 tag: ToClientTag = .time_report_compile_result,
466
467 step_idx: u32 align(1),
468
469 flags: Flags,
470 stats: Stats align(1),
471 ns_total: u64 align(1),
472
473 llvm_pass_timings_len: u32 align(1),
474 files_len: u32 align(1),
475 decls_len: u32 align(1),
476
477 pub const Flags = packed struct(u8) {
478 use_llvm: bool,
479 _: u7 = 0,
480 };
481
482 pub const Stats = extern struct {
483 n_reachable_files: u32,
484 n_imported_files: u32,
485 n_generic_instances: u32,
486 n_inline_calls: u32,
487
488 cpu_ns_parse: u64,
489 cpu_ns_astgen: u64,
490 cpu_ns_sema: u64,
491 cpu_ns_codegen: u64,
492 cpu_ns_link: u64,
493
494 real_ns_files: u64,
495 real_ns_decls: u64,
496 real_ns_llvm_emit: u64,
497 real_ns_link_flush: u64,
498
499 pub const init: Stats = .{
500 .n_reachable_files = 0,
501 .n_imported_files = 0,
502 .n_generic_instances = 0,
503 .n_inline_calls = 0,
504 .cpu_ns_parse = 0,
505 .cpu_ns_astgen = 0,
506 .cpu_ns_sema = 0,
507 .cpu_ns_codegen = 0,
508 .cpu_ns_link = 0,
509 .real_ns_files = 0,
510 .real_ns_decls = 0,
511 .real_ns_llvm_emit = 0,
512 .real_ns_link_flush = 0,
513 };
514 };
515 };
516
517 /// WebSocket server->client.
518 ///
519 /// Sent after a `Step.Run` for a Zig test executable finishes, providing the test's time report.
520 ///
521 /// Trailing:
522 /// * for each `tests_len`:
523 /// * `test_ns: u64` (nanoseconds spent running this test)
524 /// * for each `tests_len`:
525 /// * `name` (null-terminated UTF-8 string)
526 pub const RunTestResult = extern struct {
527 tag: ToClientTag = .time_report_run_test_result,
528 step_idx: u32 align(1),
529 tests_len: u32 align(1),
530 };
531};