authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-06 17:51:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-03-10 17:51:06-07:00
logc7c7ad1b78fc8f79b5a0ddebdd374630f272be9a
tree6b2e3367a359ed67e4be82f8b0a461f9a92eb2db
parent34faf9d12e3ce7b203a8e6e51e1652a985cfb626

zig std: implement serving the wasm binary


1 files changed, 199 insertions(+), 35 deletions(-)

lib/compiler/std-docs.zig+199-35
......@@ -15,9 +15,8 @@ pub fn main() !void {
1515 const zig_exe_path = args[2];
1616 const global_cache_path = args[3];
1717
18 const docs_path = try std.fs.path.join(arena, &.{ zig_lib_directory, "docs" });
19 var docs_dir = try std.fs.cwd().openDir(docs_path, .{});
20 defer docs_dir.close();
18 var lib_dir = try std.fs.cwd().openDir(zig_lib_directory, .{});
19 defer lib_dir.close();
2120
2221 const listen_port: u16 = 0;
2322 const address = std.net.Address.parseIp("127.0.0.1", listen_port) catch unreachable;
......@@ -29,6 +28,14 @@ pub fn main() !void {
2928 std.log.err("unable to open browser: {s}", .{@errorName(err)});
3029 };
3130
31 var context: Context = .{
32 .gpa = gpa,
33 .zig_exe_path = zig_exe_path,
34 .global_cache_path = global_cache_path,
35 .lib_dir = lib_dir,
36 .zig_lib_directory = zig_lib_directory,
37 };
38
3239 var read_buffer: [8000]u8 = undefined;
3340 accept: while (true) {
3441 const connection = try http_server.accept();
......@@ -43,7 +50,7 @@ pub fn main() !void {
4350 continue :accept;
4451 },
4552 };
46 serveRequest(&request, gpa, docs_dir, zig_exe_path, global_cache_path) catch |err| {
53 serveRequest(&request, &context) catch |err| {
4754 std.log.err("unable to serve {s}: {s}", .{ request.head.target, @errorName(err) });
4855 continue :accept;
4956 };
......@@ -51,25 +58,31 @@ pub fn main() !void {
5158 }
5259}
5360
54fn serveRequest(
55 request: *std.http.Server.Request,
61const Context = struct {
5662 gpa: Allocator,
57 docs_dir: std.fs.Dir,
63 lib_dir: std.fs.Dir,
64 zig_lib_directory: []const u8,
5865 zig_exe_path: []const u8,
5966 global_cache_path: []const u8,
60) !void {
67};
68
69fn serveRequest(request: *std.http.Server.Request, context: *Context) !void {
6170 if (std.mem.eql(u8, request.head.target, "/") or
6271 std.mem.eql(u8, request.head.target, "/debug/"))
6372 {
64 try serveDocsFile(request, gpa, docs_dir, "index.html", "text/html");
73 try serveDocsFile(request, context, "docs/index.html", "text/html");
6574 } else if (std.mem.eql(u8, request.head.target, "/main.js") or
6675 std.mem.eql(u8, request.head.target, "/debug/main.js"))
6776 {
68 try serveDocsFile(request, gpa, docs_dir, "main.js", "application/javascript");
77 try serveDocsFile(request, context, "docs/main.js", "application/javascript");
6978 } else if (std.mem.eql(u8, request.head.target, "/main.wasm")) {
70 try serveWasm(request, gpa, zig_exe_path, global_cache_path, .ReleaseFast);
79 try serveWasm(request, context, .ReleaseFast);
7180 } else if (std.mem.eql(u8, request.head.target, "/debug/main.wasm")) {
72 try serveWasm(request, gpa, zig_exe_path, global_cache_path, .Debug);
81 try serveWasm(request, context, .Debug);
82 } else if (std.mem.eql(u8, request.head.target, "/sources.tar") or
83 std.mem.eql(u8, request.head.target, "/debug/sources.tar"))
84 {
85 try serveSourcesTar(request, context);
7386 } else {
7487 try request.respond("not found", .{
7588 .status = .not_found,
......@@ -80,68 +93,219 @@ fn serveRequest(
8093 }
8194}
8295
96const cache_control_header: std.http.Header = .{
97 .name = "cache-control",
98 .value = "max-age=0, must-revalidate",
99};
100
83101fn serveDocsFile(
84102 request: *std.http.Server.Request,
85 gpa: Allocator,
86 docs_dir: std.fs.Dir,
103 context: *Context,
87104 name: []const u8,
88105 content_type: []const u8,
89106) !void {
107 const gpa = context.gpa;
90108 // The desired API is actually sendfile, which will require enhancing std.http.Server.
91109 // We load the file with every request so that the user can make changes to the file
92110 // and refresh the HTML page without restarting this server.
93 const file_contents = try docs_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
111 const file_contents = try context.lib_dir.readFileAlloc(gpa, name, 10 * 1024 * 1024);
94112 defer gpa.free(file_contents);
95113 try request.respond(file_contents, .{
96114 .status = .ok,
97115 .extra_headers = &.{
98116 .{ .name = "content-type", .value = content_type },
117 cache_control_header,
99118 },
100119 });
101120}
102121
122fn serveSourcesTar(request: *std.http.Server.Request, context: *Context) !void {
123 _ = request;
124 _ = context;
125 @panic("TODO");
126}
127
103128fn serveWasm(
104129 request: *std.http.Server.Request,
105 gpa: Allocator,
106 zig_exe_path: []const u8,
107 global_cache_path: []const u8,
130 context: *Context,
108131 optimize_mode: std.builtin.OptimizeMode,
109132) !void {
110 _ = request;
111 _ = gpa;
112 _ = zig_exe_path;
113 _ = global_cache_path;
114 _ = optimize_mode;
115 @panic("TODO serve wasm");
133 const gpa = context.gpa;
134
135 var arena_instance = std.heap.ArenaAllocator.init(gpa);
136 defer arena_instance.deinit();
137 const arena = arena_instance.allocator();
138
139 // Do the compilation every request, so that the user can edit the files
140 // and see the changes without restarting the server.
141 const wasm_binary_path = try buildWasmBinary(arena, context, optimize_mode);
142 // std.http.Server does not have a sendfile API yet.
143 const file_contents = try std.fs.cwd().readFileAlloc(gpa, wasm_binary_path, 10 * 1024 * 1024);
144 defer gpa.free(file_contents);
145 try request.respond(file_contents, .{
146 .status = .ok,
147 .extra_headers = &.{
148 .{ .name = "content-type", .value = "application/wasm" },
149 cache_control_header,
150 },
151 });
116152}
117153
118const BuildWasmBinaryOptions = struct {
119 zig_exe_path: []const u8,
120 global_cache_path: []const u8,
121 main_src_path: []const u8,
122};
154fn buildWasmBinary(
155 arena: Allocator,
156 context: *Context,
157 optimize_mode: std.builtin.OptimizeMode,
158) ![]const u8 {
159 const gpa = context.gpa;
160
161 const main_src_path = try std.fs.path.join(arena, &.{
162 context.zig_lib_directory, "docs", "wasm", "main.zig",
163 });
123164
124fn buildWasmBinary(arena: Allocator, options: BuildWasmBinaryOptions) ![]const u8 {
125165 var argv: std.ArrayListUnmanaged([]const u8) = .{};
166
126167 try argv.appendSlice(arena, &.{
127 options.zig_exe_path,
168 context.zig_exe_path,
128169 "build-exe",
129170 "-fno-entry",
130 "-OReleaseSmall",
171 "-O",
172 @tagName(optimize_mode),
131173 "-target",
132174 "wasm32-freestanding",
133175 "-mcpu",
134176 "baseline+atomics+bulk_memory+multivalue+mutable_globals+nontrapping_fptoint+reference_types+sign_ext",
135177 "--cache-dir",
136 options.global_cache_path,
178 context.global_cache_path,
137179 "--global-cache-dir",
138 options.global_cache_path,
180 context.global_cache_path,
139181 "--name",
140182 "autodoc",
141183 "-rdynamic",
142 options.main_src_path,
184 main_src_path,
143185 "--listen=-",
144186 });
187
188 var child = std.ChildProcess.init(argv.items, gpa);
189 child.stdin_behavior = .Pipe;
190 child.stdout_behavior = .Pipe;
191 child.stderr_behavior = .Pipe;
192 try child.spawn();
193
194 var poller = std.io.poll(gpa, enum { stdout, stderr }, .{
195 .stdout = child.stdout.?,
196 .stderr = child.stderr.?,
197 });
198 defer poller.deinit();
199
200 try sendMessage(child.stdin.?, .update);
201 try sendMessage(child.stdin.?, .exit);
202
203 const Header = std.zig.Server.Message.Header;
204 var result: ?[]const u8 = null;
205 var result_error_bundle = std.zig.ErrorBundle.empty;
206
207 const stdout = poller.fifo(.stdout);
208
209 poll: while (true) {
210 while (stdout.readableLength() < @sizeOf(Header)) {
211 if (!(try poller.poll())) break :poll;
212 }
213 const header = stdout.reader().readStruct(Header) catch unreachable;
214 while (stdout.readableLength() < header.bytes_len) {
215 if (!(try poller.poll())) break :poll;
216 }
217 const body = stdout.readableSliceOfLen(header.bytes_len);
218
219 switch (header.tag) {
220 .zig_version => {
221 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
222 return error.ZigProtocolVersionMismatch;
223 }
224 },
225 .error_bundle => {
226 const EbHdr = std.zig.Server.Message.ErrorBundle;
227 const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
228 const extra_bytes =
229 body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
230 const string_bytes =
231 body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
232 // TODO: use @ptrCast when the compiler supports it
233 const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
234 const extra_array = try arena.alloc(u32, unaligned_extra.len);
235 @memcpy(extra_array, unaligned_extra);
236 result_error_bundle = .{
237 .string_bytes = try arena.dupe(u8, string_bytes),
238 .extra = extra_array,
239 };
240 },
241 .emit_bin_path => {
242 const EbpHdr = std.zig.Server.Message.EmitBinPath;
243 const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
244 if (!ebp_hdr.flags.cache_hit) {
245 std.log.info("source changes detected; rebuilding wasm component", .{});
246 }
247 result = try arena.dupe(u8, body[@sizeOf(EbpHdr)..]);
248 },
249 else => {}, // ignore other messages
250 }
251
252 stdout.discard(body.len);
253 }
254
255 const stderr = poller.fifo(.stderr);
256 if (stderr.readableLength() > 0) {
257 const owned_stderr = try stderr.toOwnedSlice();
258 defer gpa.free(owned_stderr);
259 std.debug.print("{s}", .{owned_stderr});
260 }
261
262 // Send EOF to stdin.
263 child.stdin.?.close();
264 child.stdin = null;
265
266 switch (try child.wait()) {
267 .Exited => |code| {
268 if (code != 0) {
269 std.log.err(
270 "the following command exited with error code {d}:\n{s}",
271 .{ code, try std.Build.Step.allocPrintCmd(arena, null, argv.items) },
272 );
273 return error.AlreadyReported;
274 }
275 },
276 .Signal, .Stopped, .Unknown => {
277 std.log.err(
278 "the following command terminated unexpectedly:\n{s}",
279 .{try std.Build.Step.allocPrintCmd(arena, null, argv.items)},
280 );
281 return error.AlreadyReported;
282 },
283 }
284
285 if (result_error_bundle.errorMessageCount() > 0) {
286 const color = std.zig.Color.auto;
287 result_error_bundle.renderToStdErr(color.renderOptions());
288 std.log.err("the following command failed with {d} compilation errors:\n{s}", .{
289 result_error_bundle.errorMessageCount(),
290 try std.Build.Step.allocPrintCmd(arena, null, argv.items),
291 });
292 return error.AlreadyReported;
293 }
294
295 return result orelse {
296 std.log.err("child process failed to report result\n{s}", .{
297 try std.Build.Step.allocPrintCmd(arena, null, argv.items),
298 });
299 return error.AlreadyReported;
300 };
301}
302
303fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
304 const header: std.zig.Client.Message.Header = .{
305 .tag = tag,
306 .bytes_len = 0,
307 };
308 try file.writeAll(std.mem.asBytes(&header));
145309}
146310
147311fn openBrowserTab(gpa: Allocator, url: []const u8) !void {