master
1const std = @import("std");
2const assert = std.debug.assert;
3const abi = std.Build.abi;
4const gpa = std.heap.wasm_allocator;
5const log = std.log;
6const Allocator = std.mem.Allocator;
7
8const fuzz = @import("fuzz.zig");
9const time_report = @import("time_report.zig");
10
11/// Nanoseconds.
12var server_base_timestamp: i64 = 0;
13/// Milliseconds.
14var client_base_timestamp: i64 = 0;
15
16pub var step_list: []Step = &.{};
17/// Not accessed after initialization, but must be freed alongside `step_list`.
18pub var step_list_data: []u8 = &.{};
19
20const Step = struct {
21 name: []const u8,
22 status: abi.StepUpdate.Status,
23};
24
25const js = struct {
26 extern "core" fn log(ptr: [*]const u8, len: usize) void;
27 extern "core" fn panic(ptr: [*]const u8, len: usize) noreturn;
28 extern "core" fn timestamp() i64;
29 extern "core" fn hello(
30 steps_len: u32,
31 status: abi.BuildStatus,
32 time_report: bool,
33 ) void;
34 extern "core" fn updateBuildStatus(status: abi.BuildStatus) void;
35 extern "core" fn updateStepStatus(step_idx: u32) void;
36 extern "core" fn sendWsMessage(ptr: [*]const u8, len: usize) void;
37};
38
39pub const std_options: std.Options = .{
40 .logFn = logFn,
41};
42
43pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn {
44 _ = st;
45 _ = addr;
46 log.err("panic: {s}", .{msg});
47 @trap();
48}
49
50fn logFn(
51 comptime message_level: log.Level,
52 comptime scope: @EnumLiteral(),
53 comptime format: []const u8,
54 args: anytype,
55) void {
56 const level_txt = comptime message_level.asText();
57 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
58 var buf: [500]u8 = undefined;
59 const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: {
60 buf[buf.len - 3 ..][0..3].* = "...".*;
61 break :l &buf;
62 };
63 js.log(line.ptr, line.len);
64}
65
66export fn alloc(n: usize) [*]u8 {
67 const slice = gpa.alloc(u8, n) catch @panic("OOM");
68 return slice.ptr;
69}
70
71var message_buffer: std.ArrayListAlignedUnmanaged(u8, .of(u64)) = .empty;
72
73/// Resizes the message buffer to be the correct length; returns the pointer to
74/// the query string.
75export fn message_begin(len: usize) [*]u8 {
76 message_buffer.resize(gpa, len) catch @panic("OOM");
77 return message_buffer.items.ptr;
78}
79
80export fn message_end() void {
81 const msg_bytes = message_buffer.items;
82
83 const tag: abi.ToClientTag = @enumFromInt(msg_bytes[0]);
84 switch (tag) {
85 _ => @panic("malformed message"),
86
87 .hello => return helloMessage(msg_bytes) catch @panic("OOM"),
88 .status_update => return statusUpdateMessage(msg_bytes) catch @panic("OOM"),
89 .step_update => return stepUpdateMessage(msg_bytes) catch @panic("OOM"),
90
91 .fuzz_source_index => return fuzz.sourceIndexMessage(msg_bytes) catch @panic("OOM"),
92 .fuzz_coverage_update => return fuzz.coverageUpdateMessage(msg_bytes) catch @panic("OOM"),
93 .fuzz_entry_points => return fuzz.entryPointsMessage(msg_bytes) catch @panic("OOM"),
94
95 .time_report_generic_result => return time_report.genericResultMessage(msg_bytes) catch @panic("OOM"),
96 .time_report_compile_result => return time_report.compileResultMessage(msg_bytes) catch @panic("OOM"),
97 .time_report_run_test_result => return time_report.runTestResultMessage(msg_bytes) catch @panic("OOM"),
98 }
99}
100
101const String = Slice(u8);
102
103pub fn Slice(T: type) type {
104 return packed struct(u64) {
105 ptr: u32,
106 len: u32,
107
108 pub fn init(s: []const T) @This() {
109 return .{
110 .ptr = @intFromPtr(s.ptr),
111 .len = s.len,
112 };
113 }
114 };
115}
116
117pub fn fatal(comptime format: []const u8, args: anytype) noreturn {
118 var buf: [500]u8 = undefined;
119 const line = std.fmt.bufPrint(&buf, format, args) catch l: {
120 buf[buf.len - 3 ..][0..3].* = "...".*;
121 break :l &buf;
122 };
123 js.panic(line.ptr, line.len);
124}
125
126fn helloMessage(msg_bytes: []align(4) u8) Allocator.Error!void {
127 if (msg_bytes.len < @sizeOf(abi.Hello)) @panic("malformed Hello message");
128 const hdr: *const abi.Hello = @ptrCast(msg_bytes[0..@sizeOf(abi.Hello)]);
129 const trailing = msg_bytes[@sizeOf(abi.Hello)..];
130
131 client_base_timestamp = js.timestamp();
132 server_base_timestamp = hdr.timestamp;
133
134 const steps = try gpa.alloc(Step, hdr.steps_len);
135 errdefer gpa.free(steps);
136
137 const step_name_lens: []align(1) const u32 = @ptrCast(trailing[0 .. steps.len * 4]);
138
139 const step_name_data_len: usize = len: {
140 var sum: usize = 0;
141 for (step_name_lens) |n| sum += n;
142 break :len sum;
143 };
144 const step_name_data: []const u8 = trailing[steps.len * 4 ..][0..step_name_data_len];
145 const step_status_bits: []const u8 = trailing[steps.len * 4 + step_name_data_len ..];
146
147 const duped_step_name_data = try gpa.dupe(u8, step_name_data);
148 errdefer gpa.free(duped_step_name_data);
149
150 var name_off: usize = 0;
151 for (steps, step_name_lens, 0..) |*step_out, name_len, step_idx| {
152 step_out.* = .{
153 .name = duped_step_name_data[name_off..][0..name_len],
154 .status = @enumFromInt(@as(u2, @truncate(step_status_bits[step_idx / 4] >> @intCast((step_idx % 4) * 2)))),
155 };
156 name_off += name_len;
157 }
158
159 gpa.free(step_list);
160 gpa.free(step_list_data);
161 step_list = steps;
162 step_list_data = duped_step_name_data;
163
164 js.hello(step_list.len, hdr.status, hdr.flags.time_report);
165}
166fn statusUpdateMessage(msg_bytes: []u8) Allocator.Error!void {
167 if (msg_bytes.len < @sizeOf(abi.StatusUpdate)) @panic("malformed StatusUpdate message");
168 const msg: *const abi.StatusUpdate = @ptrCast(msg_bytes[0..@sizeOf(abi.StatusUpdate)]);
169 js.updateBuildStatus(msg.new);
170}
171fn stepUpdateMessage(msg_bytes: []u8) Allocator.Error!void {
172 if (msg_bytes.len < @sizeOf(abi.StepUpdate)) @panic("malformed StepUpdate message");
173 const msg: *const abi.StepUpdate = @ptrCast(msg_bytes[0..@sizeOf(abi.StepUpdate)]);
174 if (msg.step_idx >= step_list.len) @panic("malformed StepUpdate message");
175 step_list[msg.step_idx].status = msg.bits.status;
176 js.updateStepStatus(msg.step_idx);
177}
178
179export fn stepName(idx: usize) String {
180 return .init(step_list[idx].name);
181}
182export fn stepStatus(idx: usize) u8 {
183 return @intFromEnum(step_list[idx].status);
184}
185
186export fn rebuild() void {
187 const msg: abi.Rebuild = .{};
188 const raw: []const u8 = @ptrCast(&msg);
189 js.sendWsMessage(raw.ptr, raw.len);
190}
191
192/// Nanoseconds passed since a server timestamp.
193pub fn nsSince(server_timestamp: i64) i64 {
194 const ms_passed = js.timestamp() - client_base_timestamp;
195 const ns_passed = server_base_timestamp - server_timestamp;
196 return ns_passed + ms_passed * std.time.ns_per_ms;
197}
198
199pub fn fmtEscapeHtml(unescaped: []const u8) HtmlEscaper {
200 return .{ .unescaped = unescaped };
201}
202const HtmlEscaper = struct {
203 unescaped: []const u8,
204 pub fn format(he: HtmlEscaper, w: *std.Io.Writer) !void {
205 for (he.unescaped) |c| switch (c) {
206 '&' => try w.writeAll("&"),
207 '<' => try w.writeAll("<"),
208 '>' => try w.writeAll(">"),
209 '"' => try w.writeAll("""),
210 '\'' => try w.writeAll("'"),
211 else => try w.writeByte(c),
212 };
213 }
214};