master
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
9
10// Check that no WebSocket message type has implicit padding bits. This ensures we never send any
11// undefined bits over the wire, and also helps validate that the layout doesn't differ between, for
12// instance, the web server in `std.Build` and the Wasm client.
13comptime {
14 const check = struct {
15 fn check(comptime T: type) void {
16 const std = @import("std");
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 pub const TestOne = *const fn (Slice) callconv(.c) void;
143 pub extern fn fuzzer_init(cache_dir_path: Slice) void;
144 pub extern fn fuzzer_coverage() Coverage;
145 pub extern fn fuzzer_init_test(test_one: TestOne, unit_test_name: Slice) void;
146 pub extern fn fuzzer_new_input(bytes: Slice) void;
147 pub extern fn fuzzer_main(limit_kind: LimitKind, amount: u64) void;
148 pub extern fn fuzzer_unslide_address(addr: usize) usize;
149
150 pub const Slice = extern struct {
151 ptr: [*]const u8,
152 len: usize,
153
154 pub fn toSlice(s: Slice) []const u8 {
155 return s.ptr[0..s.len];
156 }
157
158 pub fn fromSlice(s: []const u8) Slice {
159 return .{ .ptr = s.ptr, .len = s.len };
160 }
161 };
162
163 pub const LimitKind = enum(u8) { forever, iterations };
164
165 /// libfuzzer uses this and its usize is the one that counts. To match the ABI,
166 /// make the ints be the size of the target used with libfuzzer.
167 ///
168 /// Trailing:
169 /// * 1 bit per pc_addr, usize elements
170 /// * pc_addr: usize for each pcs_len
171 pub const SeenPcsHeader = extern struct {
172 n_runs: usize,
173 unique_runs: usize,
174 pcs_len: usize,
175
176 /// Used for comptime assertions. Provides a mechanism for strategically
177 /// causing compile errors.
178 pub const trailing = .{
179 .pc_bits_usize,
180 .pc_addr,
181 };
182
183 pub fn headerEnd(header: *const SeenPcsHeader) []const usize {
184 const ptr: [*]align(@alignOf(usize)) const u8 = @ptrCast(header);
185 const header_end_ptr: [*]const usize = @ptrCast(ptr + @sizeOf(SeenPcsHeader));
186 const pcs_len = header.pcs_len;
187 return header_end_ptr[0 .. pcs_len + seenElemsLen(pcs_len)];
188 }
189
190 pub fn seenBits(header: *const SeenPcsHeader) []const usize {
191 return header.headerEnd()[0..seenElemsLen(header.pcs_len)];
192 }
193
194 pub fn seenElemsLen(pcs_len: usize) usize {
195 return (pcs_len + @bitSizeOf(usize) - 1) / @bitSizeOf(usize);
196 }
197
198 pub fn pcAddrs(header: *const SeenPcsHeader) []const usize {
199 const pcs_len = header.pcs_len;
200 return header.headerEnd()[seenElemsLen(pcs_len)..][0..pcs_len];
201 }
202 };
203
204 /// WebSocket server->client.
205 ///
206 /// Sent once, when fuzzing starts, to indicate the available coverage data.
207 ///
208 /// Trailing:
209 /// * std.debug.Coverage.String for each directories_len
210 /// * std.debug.Coverage.File for each files_len
211 /// * std.debug.Coverage.SourceLocation for each source_locations_len
212 /// * u8 for each string_bytes_len
213 pub const SourceIndexHeader = extern struct {
214 tag: ToClientTag = .fuzz_source_index,
215 _: [3]u8 = @splat(0),
216 directories_len: u32,
217 files_len: u32,
218 source_locations_len: u32,
219 string_bytes_len: u32,
220 /// When, according to the server, fuzzing started.
221 start_timestamp: i64 align(4),
222 };
223
224 /// WebSocket server->client.
225 ///
226 /// Sent whenever the set of covered source locations is updated.
227 ///
228 /// Trailing:
229 /// * one bit per source_locations_len, contained in u64 elements
230 pub const CoverageUpdateHeader = extern struct {
231 tag: ToClientTag = .fuzz_coverage_update,
232 _: [7]u8 = @splat(0),
233 n_runs: u64,
234 unique_runs: u64,
235
236 pub const trailing = .{
237 .pc_bits_usize,
238 };
239 };
240
241 /// WebSocket server->client.
242 ///
243 /// Sent whenever the set of entry points is updated.
244 ///
245 /// Trailing:
246 /// * one u32 index of source_locations per locsLen()
247 pub const EntryPointHeader = extern struct {
248 tag: ToClientTag = .fuzz_entry_points,
249 locs_len_raw: [3]u8,
250
251 pub fn locsLen(hdr: EntryPointHeader) u24 {
252 return @bitCast(hdr.locs_len_raw);
253 }
254 pub fn init(locs_len: u24) EntryPointHeader {
255 return .{ .locs_len_raw = @bitCast(locs_len) };
256 }
257 };
258
259 /// Sent by lib/fuzzer to test_runner to obtain information about the
260 /// active memory mapped input file and cumulative stats about previous
261 /// fuzzing runs.
262 pub const Coverage = extern struct {
263 id: u64,
264 runs: u64,
265 unique: u64,
266 seen: u64,
267 };
268};
269
270/// ABI bits specifically relating to the time report interface.
271pub const time_report = struct {
272 /// WebSocket server->client.
273 ///
274 /// Sent after a `Step` finishes, providing the time taken to execute the step.
275 pub const GenericResult = extern struct {
276 tag: ToClientTag = .time_report_generic_result,
277 step_idx: u32 align(1),
278 ns_total: u64 align(1),
279 };
280
281 /// WebSocket server->client.
282 ///
283 /// Sent after a `Step.Compile` finishes, providing the step's time report.
284 ///
285 /// Trailing:
286 /// * `llvm_pass_timings: [llvm_pass_timings_len]u8` (ASCII-encoded)
287 /// * for each `files_len`:
288 /// * `name` (null-terminated UTF-8 string)
289 /// * for each `decls_len`:
290 /// * `name` (null-terminated UTF-8 string)
291 /// * `file: u32` (index of file this decl is in)
292 /// * `sema_ns: u64` (nanoseconds spent semantically analyzing this decl)
293 /// * `codegen_ns: u64` (nanoseconds spent semantically analyzing this decl)
294 /// * `link_ns: u64` (nanoseconds spent semantically analyzing this decl)
295 pub const CompileResult = extern struct {
296 tag: ToClientTag = .time_report_compile_result,
297
298 step_idx: u32 align(1),
299
300 flags: Flags,
301 stats: Stats align(1),
302 ns_total: u64 align(1),
303
304 llvm_pass_timings_len: u32 align(1),
305 files_len: u32 align(1),
306 decls_len: u32 align(1),
307
308 pub const Flags = packed struct(u8) {
309 use_llvm: bool,
310 _: u7 = 0,
311 };
312
313 pub const Stats = extern struct {
314 n_reachable_files: u32,
315 n_imported_files: u32,
316 n_generic_instances: u32,
317 n_inline_calls: u32,
318
319 cpu_ns_parse: u64,
320 cpu_ns_astgen: u64,
321 cpu_ns_sema: u64,
322 cpu_ns_codegen: u64,
323 cpu_ns_link: u64,
324
325 real_ns_files: u64,
326 real_ns_decls: u64,
327 real_ns_llvm_emit: u64,
328 real_ns_link_flush: u64,
329
330 pub const init: Stats = .{
331 .n_reachable_files = 0,
332 .n_imported_files = 0,
333 .n_generic_instances = 0,
334 .n_inline_calls = 0,
335 .cpu_ns_parse = 0,
336 .cpu_ns_astgen = 0,
337 .cpu_ns_sema = 0,
338 .cpu_ns_codegen = 0,
339 .cpu_ns_link = 0,
340 .real_ns_files = 0,
341 .real_ns_decls = 0,
342 .real_ns_llvm_emit = 0,
343 .real_ns_link_flush = 0,
344 };
345 };
346 };
347
348 /// WebSocket server->client.
349 ///
350 /// Sent after a `Step.Run` for a Zig test executable finishes, providing the test's time report.
351 ///
352 /// Trailing:
353 /// * for each `tests_len`:
354 /// * `test_ns: u64` (nanoseconds spent running this test)
355 /// * for each `tests_len`:
356 /// * `name` (null-terminated UTF-8 string)
357 pub const RunTestResult = extern struct {
358 tag: ToClientTag = .time_report_run_test_result,
359 step_idx: u32 align(1),
360 tests_len: u32 align(1),
361 };
362};