authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-24 19:12:44-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-24 19:12:44-07:00
log8c4482ed78fb651c0288f0cd2bdaf328564c6a49
treeefccaf6ea986a840f3cf4df64da174b7bdd18da5
parentdfbb6e9879693331c7b8ea57eb7aa8bdf934403f
parent4236ca40cd21895590c580c9a1f56423c2c2f167

Merge remote-tracking branch 'origin/master' into wrangle-writer-buffering


16 files changed, 428 insertions(+), 315 deletions(-)

lib/std/Build/Fuzz/WebServer.zig+8-7
...@@ -282,13 +282,15 @@ fn buildWasmBinary(...@@ -282,13 +282,15 @@ fn buildWasmBinary(
282 var result: ?Path = null;282 var result: ?Path = null;
283 var result_error_bundle = std.zig.ErrorBundle.empty;283 var result_error_bundle = std.zig.ErrorBundle.empty;
284284
285 const stdout_br = poller.reader(.stdout);285 const stdout = poller.reader(.stdout);
286
286 poll: while (true) {287 poll: while (true) {
287 const Header = std.zig.Server.Message.Header;288 const Header = std.zig.Server.Message.Header;
288 while (stdout_br.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll;289 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll;
289 const header = (stdout_br.takeStruct(Header) catch unreachable).*;290 const header = stdout.takeStruct(Header, .little) catch unreachable;
290 while (stdout_br.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;291 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;
291 const body = stdout_br.take(header.bytes_len) catch unreachable;292 const body = stdout.take(header.bytes_len) catch unreachable;
293
292 switch (header.tag) {294 switch (header.tag) {
293 .zig_version => {295 .zig_version => {
294 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {296 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
...@@ -327,8 +329,7 @@ fn buildWasmBinary(...@@ -327,8 +329,7 @@ fn buildWasmBinary(
327 }329 }
328 }330 }
329331
330 const stderr_br = poller.reader(.stderr);332 const stderr_contents = try poller.toOwnedSlice(.stderr);
331 const stderr_contents = stderr_br.buffered();
332 if (stderr_contents.len > 0) {333 if (stderr_contents.len > 0) {
333 std.debug.print("{s}", .{stderr_contents});334 std.debug.print("{s}", .{stderr_contents});
334 }335 }
lib/std/Build/Step.zig+17-16
...@@ -286,7 +286,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {...@@ -286,7 +286,7 @@ pub fn cast(step: *Step, comptime T: type) ?*T {
286}286}
287287
288/// For debugging purposes, prints identifying information about this Step.288/// For debugging purposes, prints identifying information about this Step.
289pub fn dump(step: *Step, w: *std.io.Writer, tty_config: std.io.tty.Config) void {289pub fn dump(step: *Step, w: *std.Io.Writer, tty_config: std.Io.tty.Config) void {
290 const debug_info = std.debug.getSelfDebugInfo() catch |err| {290 const debug_info = std.debug.getSelfDebugInfo() catch |err| {
291 w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{291 w.print("Unable to dump stack trace: Unable to open debug info: {s}\n", .{
292 @errorName(err),292 @errorName(err),
...@@ -359,7 +359,7 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO...@@ -359,7 +359,7 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO
359359
360pub const ZigProcess = struct {360pub const ZigProcess = struct {
361 child: std.process.Child,361 child: std.process.Child,
362 poller: std.io.Poller(StreamEnum),362 poller: std.Io.Poller(StreamEnum),
363 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,363 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
364364
365 pub const StreamEnum = enum { stdout, stderr };365 pub const StreamEnum = enum { stdout, stderr };
...@@ -428,7 +428,7 @@ pub fn evalZigProcess(...@@ -428,7 +428,7 @@ pub fn evalZigProcess(
428 const zp = try gpa.create(ZigProcess);428 const zp = try gpa.create(ZigProcess);
429 zp.* = .{429 zp.* = .{
430 .child = child,430 .child = child,
431 .poller = std.io.poll(gpa, ZigProcess.StreamEnum, .{431 .poller = std.Io.poll(gpa, ZigProcess.StreamEnum, .{
432 .stdout = child.stdout.?,432 .stdout = child.stdout.?,
433 .stderr = child.stderr.?,433 .stderr = child.stderr.?,
434 }),434 }),
...@@ -511,12 +511,14 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path {...@@ -511,12 +511,14 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path {
511 var result: ?Path = null;511 var result: ?Path = null;
512512
513 const stdout = zp.poller.reader(.stdout);513 const stdout = zp.poller.reader(.stdout);
514
514 poll: while (true) {515 poll: while (true) {
515 const Header = std.zig.Server.Message.Header;516 const Header = std.zig.Server.Message.Header;
516 while (stdout.buffered().len < @sizeOf(Header)) if (!try zp.poller.poll()) break :poll;517 while (stdout.buffered().len < @sizeOf(Header)) if (!try zp.poller.poll()) break :poll;
517 const header = (stdout.takeStruct(Header) catch unreachable).*;518 const header = stdout.takeStruct(Header, .little) catch unreachable;
518 while (stdout.buffered().len < header.bytes_len) if (!try zp.poller.poll()) break :poll;519 while (stdout.buffered().len < header.bytes_len) if (!try zp.poller.poll()) break :poll;
519 const body = stdout.take(header.bytes_len) catch unreachable;520 const body = stdout.take(header.bytes_len) catch unreachable;
521
520 switch (header.tag) {522 switch (header.tag) {
521 .zig_version => {523 .zig_version => {
522 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {524 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
...@@ -606,8 +608,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path {...@@ -606,8 +608,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool) !?Path {
606608
607 s.result_duration_ns = timer.read();609 s.result_duration_ns = timer.read();
608610
609 const stderr = zp.poller.reader(.stderr);611 const stderr_contents = try zp.poller.toOwnedSlice(.stderr);
610 const stderr_contents = stderr.buffered();
611 if (stderr_contents.len > 0) {612 if (stderr_contents.len > 0) {
612 try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents));613 try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents));
613 }614 }
...@@ -726,7 +727,7 @@ pub fn allocPrintCmd2(...@@ -726,7 +727,7 @@ pub fn allocPrintCmd2(
726 argv: []const []const u8,727 argv: []const []const u8,
727) Allocator.Error![]u8 {728) Allocator.Error![]u8 {
728 const shell = struct {729 const shell = struct {
729 fn escape(writer: *std.io.Writer, string: []const u8, is_argv0: bool) !void {730 fn escape(writer: *std.Io.Writer, string: []const u8, is_argv0: bool) !void {
730 for (string) |c| {731 for (string) |c| {
731 if (switch (c) {732 if (switch (c) {
732 else => true,733 else => true,
...@@ -760,9 +761,9 @@ pub fn allocPrintCmd2(...@@ -760,9 +761,9 @@ pub fn allocPrintCmd2(
760 }761 }
761 };762 };
762763
763 var aw: std.io.Writer.Allocating = .init(arena);764 var aw: std.Io.Writer.Allocating = .init(arena);
764 const writer = &aw.writer;765 const writer = &aw.writer;
765 if (opt_cwd) |cwd| try writer.print(arena, "cd {s} && ", .{cwd});766 if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory;
766 if (opt_env) |env| {767 if (opt_env) |env| {
767 const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);768 const process_env_map = std.process.getEnvMap(arena) catch std.process.EnvMap.init(arena);
768 var it = env.iterator();769 var it = env.iterator();
...@@ -772,17 +773,17 @@ pub fn allocPrintCmd2(...@@ -772,17 +773,17 @@ pub fn allocPrintCmd2(
772 if (process_env_map.get(key)) |process_value| {773 if (process_env_map.get(key)) |process_value| {
773 if (std.mem.eql(u8, value, process_value)) continue;774 if (std.mem.eql(u8, value, process_value)) continue;
774 }775 }
775 try writer.print(arena, "{s}=", .{key});776 writer.print("{s}=", .{key}) catch return error.OutOfMemory;
776 try shell.escape(writer, value, false);777 shell.escape(writer, value, false) catch return error.OutOfMemory;
777 try writer.writeByte(arena, ' ');778 writer.writeByte(' ') catch return error.OutOfMemory;
778 }779 }
779 }780 }
780 try shell.escape(writer, argv[0], true);781 shell.escape(writer, argv[0], true) catch return error.OutOfMemory;
781 for (argv[1..]) |arg| {782 for (argv[1..]) |arg| {
782 try writer.writeByte(arena, ' ');783 writer.writeByte(' ') catch return error.OutOfMemory;
783 try shell.escape(writer, arg, false);784 shell.escape(writer, arg, false) catch return error.OutOfMemory;
784 }785 }
785 return aw.getWritten();786 return aw.toOwnedSlice();
786}787}
787788
788/// Prefer `cacheHitAndWatch` unless you already added watch inputs789/// Prefer `cacheHitAndWatch` unless you already added watch inputs
lib/std/Build/Step/Run.zig+11-9
...@@ -1545,7 +1545,7 @@ fn evalZigTest(...@@ -1545,7 +1545,7 @@ fn evalZigTest(
1545 const any_write_failed = first_write_failed or poll: while (true) {1545 const any_write_failed = first_write_failed or poll: while (true) {
1546 const Header = std.zig.Server.Message.Header;1546 const Header = std.zig.Server.Message.Header;
1547 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll false;1547 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll false;
1548 const header = (stdout.takeStruct(Header, .little) catch unreachable).*;1548 const header = stdout.takeStruct(Header, .little) catch unreachable;
1549 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll false;1549 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll false;
1550 const body = stdout.take(header.bytes_len) catch unreachable;1550 const body = stdout.take(header.bytes_len) catch unreachable;
1551 switch (header.tag) {1551 switch (header.tag) {
...@@ -1808,21 +1808,23 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult {...@@ -1808,21 +1808,23 @@ fn evalGeneric(run: *Run, child: *std.process.Child) !StdIoResult {
1808 }1808 }
1809 }1809 }
18101810
1811 stdout_bytes = poller.reader(.stdout).buffered();1811 stdout_bytes = try poller.toOwnedSlice(.stdout);
1812 stderr_bytes = poller.reader(.stderr).buffered();1812 stderr_bytes = try poller.toOwnedSlice(.stderr);
1813 } else {1813 } else {
1814 var fr = stdout.readerStreaming();1814 var small_buffer: [1]u8 = undefined;
1815 stdout_bytes = fr.interface().allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {1815 var stdout_reader = stdout.readerStreaming(&small_buffer);
1816 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
1816 error.OutOfMemory => return error.OutOfMemory,1817 error.OutOfMemory => return error.OutOfMemory,
1817 error.ReadFailed => return fr.err.?,1818 error.ReadFailed => return stdout_reader.err.?,
1818 error.StreamTooLong => return error.StdoutStreamTooLong,1819 error.StreamTooLong => return error.StdoutStreamTooLong,
1819 };1820 };
1820 }1821 }
1821 } else if (child.stderr) |stderr| {1822 } else if (child.stderr) |stderr| {
1822 var fr = stderr.readerStreaming();1823 var small_buffer: [1]u8 = undefined;
1823 stderr_bytes = fr.interface().allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {1824 var stderr_reader = stderr.readerStreaming(&small_buffer);
1825 stderr_bytes = stderr_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
1824 error.OutOfMemory => return error.OutOfMemory,1826 error.OutOfMemory => return error.OutOfMemory,
1825 error.ReadFailed => return fr.err.?,1827 error.ReadFailed => return stderr_reader.err.?,
1826 error.StreamTooLong => return error.StderrStreamTooLong,1828 error.StreamTooLong => return error.StderrStreamTooLong,
1827 };1829 };
1828 }1830 }
lib/std/Io.zig+196-143
...@@ -92,12 +92,7 @@ pub fn poll(...@@ -92,12 +92,7 @@ pub fn poll(
92 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;92 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
93 var result: Poller(StreamEnum) = .{93 var result: Poller(StreamEnum) = .{
94 .gpa = gpa,94 .gpa = gpa,
95 .readers = @splat(.{95 .readers = @splat(.failing),
96 .unbuffered_reader = .failing,
97 .buffer = &.{},
98 .end = 0,
99 .seek = 0,
100 }),
101 .poll_fds = undefined,96 .poll_fds = undefined,
102 .windows = if (is_windows) .{97 .windows = if (is_windows) .{
103 .first_read_done = false,98 .first_read_done = false,
...@@ -186,21 +181,40 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -186,21 +181,40 @@ pub fn Poller(comptime StreamEnum: type) type {
186 }181 }
187 }182 }
188183
189 pub inline fn reader(self: *Self, comptime which: StreamEnum) *Reader {184 pub fn reader(self: *Self, which: StreamEnum) *Reader {
190 return &self.readers[@intFromEnum(which)];185 return &self.readers[@intFromEnum(which)];
191 }186 }
192187
188 pub fn toOwnedSlice(self: *Self, which: StreamEnum) error{OutOfMemory}![]u8 {
189 const gpa = self.gpa;
190 const r = reader(self, which);
191 if (r.seek == 0) {
192 const new = try gpa.realloc(r.buffer, r.end);
193 r.buffer = &.{};
194 r.end = 0;
195 return new;
196 }
197 const new = try gpa.dupe(u8, r.buffered());
198 gpa.free(r.buffer);
199 r.buffer = &.{};
200 r.seek = 0;
201 r.end = 0;
202 return new;
203 }
204
193 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {205 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
194 const bump_amt = 512;206 const bump_amt = 512;
207 const gpa = self.gpa;
195208
196 if (!self.windows.first_read_done) {209 if (!self.windows.first_read_done) {
197 var already_read_data = false;210 var already_read_data = false;
198 for (0..enum_fields.len) |i| {211 for (0..enum_fields.len) |i| {
199 const handle = self.windows.active.handles_buf[i];212 const handle = self.windows.active.handles_buf[i];
200 switch (try windowsAsyncReadToFifoAndQueueSmallRead(213 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
214 gpa,
201 handle,215 handle,
202 &self.windows.overlapped[i],216 &self.windows.overlapped[i],
203 &self.fifos[i],217 &self.readers[i],
204 &self.windows.small_bufs[i],218 &self.windows.small_bufs[i],
205 bump_amt,219 bump_amt,
206 )) {220 )) {
...@@ -247,7 +261,7 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -247,7 +261,7 @@ pub fn Poller(comptime StreamEnum: type) type {
247 const handle = self.windows.active.handles_buf[active_idx];261 const handle = self.windows.active.handles_buf[active_idx];
248262
249 const overlapped = &self.windows.overlapped[stream_idx];263 const overlapped = &self.windows.overlapped[stream_idx];
250 const stream_fifo = &self.fifos[stream_idx];264 const stream_reader = &self.readers[stream_idx];
251 const small_buf = &self.windows.small_bufs[stream_idx];265 const small_buf = &self.windows.small_bufs[stream_idx];
252266
253 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {267 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
...@@ -258,12 +272,16 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -258,12 +272,16 @@ pub fn Poller(comptime StreamEnum: type) type {
258 },272 },
259 .aborted => unreachable,273 .aborted => unreachable,
260 };274 };
261 try stream_fifo.write(small_buf[0..num_bytes_read]);275 const buf = small_buf[0..num_bytes_read];
276 const dest = try writableSliceGreedyAlloc(stream_reader, gpa, buf.len);
277 @memcpy(dest[0..buf.len], buf);
278 advanceBufferEnd(stream_reader, buf.len);
262279
263 switch (try windowsAsyncReadToFifoAndQueueSmallRead(280 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
281 gpa,
264 handle,282 handle,
265 overlapped,283 overlapped,
266 stream_fifo,284 stream_reader,
267 small_buf,285 small_buf,
268 bump_amt,286 bump_amt,
269 )) {287 )) {
...@@ -298,18 +316,18 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -298,18 +316,18 @@ pub fn Poller(comptime StreamEnum: type) type {
298 }316 }
299317
300 var keep_polling = false;318 var keep_polling = false;
301 inline for (&self.poll_fds, &self.readers) |*poll_fd, *r| {319 for (&self.poll_fds, &self.readers) |*poll_fd, *r| {
302 // Try reading whatever is available before checking the error320 // Try reading whatever is available before checking the error
303 // conditions.321 // conditions.
304 // It's still possible to read after a POLL.HUP is received,322 // It's still possible to read after a POLL.HUP is received,
305 // always check if there's some data waiting to be read first.323 // always check if there's some data waiting to be read first.
306 if (poll_fd.revents & posix.POLL.IN != 0) {324 if (poll_fd.revents & posix.POLL.IN != 0) {
307 const buf = try r.writableSliceGreedyAlloc(gpa, bump_amt);325 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);
308 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {326 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
309 error.BrokenPipe => 0, // Handle the same as EOF.327 error.BrokenPipe => 0, // Handle the same as EOF.
310 else => |e| return e,328 else => |e| return e,
311 };329 };
312 r.advanceBufferEnd(amt);330 advanceBufferEnd(r, amt);
313 if (amt == 0) {331 if (amt == 0) {
314 // Remove the fd when the EOF condition is met.332 // Remove the fd when the EOF condition is met.
315 poll_fd.fd = -1;333 poll_fd.fd = -1;
...@@ -325,146 +343,181 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -325,146 +343,181 @@ pub fn Poller(comptime StreamEnum: type) type {
325 }343 }
326 return keep_polling;344 return keep_polling;
327 }345 }
328 };
329}
330346
331/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful347 /// Returns a slice into the unused capacity of `buffer` with at least
332/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For348 /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
333/// compatibility, we point it to this dummy variables, which we never otherwise access.349 ///
334/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile350 /// After calling this function, typically the caller will follow up with a
335var win_dummy_bytes_read: u32 = undefined;351 /// call to `advanceBufferEnd` to report the actual number of bytes buffered.
336352 fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
337/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before353 {
338/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data354 const unused = r.buffer[r.end..];
339/// is available. `handle` must have no pending asynchronous operation.355 if (unused.len >= min_len) return unused;
340fn windowsAsyncReadToFifoAndQueueSmallRead(356 }
341 handle: windows.HANDLE,357 if (r.seek > 0) r.rebase();
342 overlapped: *windows.OVERLAPPED,358 {
343 r: *Reader,359 var list: std.ArrayListUnmanaged(u8) = .{
344 small_buf: *[128]u8,360 .items = r.buffer[0..r.end],
345 bump_amt: usize,361 .capacity = r.buffer.len,
346) !enum { empty, populated, closed_populated, closed } {362 };
347 var read_any_data = false;363 defer r.buffer = list.allocatedSlice();
348 while (true) {364 try list.ensureUnusedCapacity(allocator, min_len);
349 const fifo_read_pending = while (true) {365 }
350 const buf = try r.writableWithSize(bump_amt);366 const unused = r.buffer[r.end..];
351 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);367 assert(unused.len >= min_len);
352368 return unused;
353 if (0 == windows.kernel32.ReadFile(369 }
354 handle,
355 buf.ptr,
356 buf_len,
357 &win_dummy_bytes_read,
358 overlapped,
359 )) switch (windows.GetLastError()) {
360 .IO_PENDING => break true,
361 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
362 else => |err| return windows.unexpectedError(err),
363 };
364370
365 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {371 /// After writing directly into the unused capacity of `buffer`, this function
366 .success => |n| n,372 /// updates `end` so that users of `Reader` can receive the data.
367 .closed => return if (read_any_data) .closed_populated else .closed,373 fn advanceBufferEnd(r: *Reader, n: usize) void {
368 .aborted => unreachable,374 assert(n <= r.buffer.len - r.end);
369 };375 r.end += n;
376 }
370377
371 read_any_data = true;378 /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
372 r.update(num_bytes_read);379 /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
380 /// compatibility, we point it to this dummy variables, which we never otherwise access.
381 /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
382 var win_dummy_bytes_read: u32 = undefined;
383
384 /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
385 /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
386 /// is available. `handle` must have no pending asynchronous operation.
387 fn windowsAsyncReadToFifoAndQueueSmallRead(
388 gpa: Allocator,
389 handle: windows.HANDLE,
390 overlapped: *windows.OVERLAPPED,
391 r: *Reader,
392 small_buf: *[128]u8,
393 bump_amt: usize,
394 ) !enum { empty, populated, closed_populated, closed } {
395 var read_any_data = false;
396 while (true) {
397 const fifo_read_pending = while (true) {
398 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);
399 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
373400
374 if (num_bytes_read == buf_len) {401 if (0 == windows.kernel32.ReadFile(
375 // We filled the buffer, so there's probably more data available.402 handle,
376 continue;403 buf.ptr,
377 } else {404 buf_len,
378 // We didn't fill the buffer, so assume we're out of data.405 &win_dummy_bytes_read,
379 // There is no pending read.406 overlapped,
380 break false;407 )) switch (windows.GetLastError()) {
381 }408 .IO_PENDING => break true,
382 };409 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
410 else => |err| return windows.unexpectedError(err),
411 };
383412
384 if (fifo_read_pending) cancel_read: {413 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
385 // Cancel the pending read into the FIFO.414 .success => |n| n,
386 _ = windows.kernel32.CancelIo(handle);415 .closed => return if (read_any_data) .closed_populated else .closed,
416 .aborted => unreachable,
417 };
387418
388 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.419 read_any_data = true;
389 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {420 advanceBufferEnd(r, num_bytes_read);
390 windows.WAIT_OBJECT_0 => {},
391 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
392 else => unreachable,
393 }
394421
395 // If it completed before we canceled, make sure to tell the FIFO!422 if (num_bytes_read == buf_len) {
396 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {423 // We filled the buffer, so there's probably more data available.
397 .success => |n| n,424 continue;
398 .closed => return if (read_any_data) .closed_populated else .closed,425 } else {
399 .aborted => break :cancel_read,426 // We didn't fill the buffer, so assume we're out of data.
400 };427 // There is no pending read.
401 read_any_data = true;428 break false;
402 r.update(num_bytes_read);429 }
403 }430 };
404431
405 // Try to queue the 1-byte read.432 if (fifo_read_pending) cancel_read: {
406 if (0 == windows.kernel32.ReadFile(433 // Cancel the pending read into the FIFO.
407 handle,434 _ = windows.kernel32.CancelIo(handle);
408 small_buf,
409 small_buf.len,
410 &win_dummy_bytes_read,
411 overlapped,
412 )) switch (windows.GetLastError()) {
413 .IO_PENDING => {
414 // 1-byte read pending as intended
415 return if (read_any_data) .populated else .empty;
416 },
417 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
418 else => |err| return windows.unexpectedError(err),
419 };
420435
421 // We got data back this time. Write it to the FIFO and run the main loop again.436 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
422 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {437 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
423 .success => |n| n,438 windows.WAIT_OBJECT_0 => {},
424 .closed => return if (read_any_data) .closed_populated else .closed,439 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
425 .aborted => unreachable,440 else => unreachable,
426 };441 }
427 try r.write(small_buf[0..num_bytes_read]);
428 read_any_data = true;
429 }
430}
431442
432/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.443 // If it completed before we canceled, make sure to tell the FIFO!
433/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).444 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
434///445 .success => |n| n,
435/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the446 .closed => return if (read_any_data) .closed_populated else .closed,
436/// operation immediately returns data:447 .aborted => break :cancel_read,
437/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially448 };
438/// erroneous results."449 read_any_data = true;
439/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]450 advanceBufferEnd(r, num_bytes_read);
440/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to451 }
441/// get the actual number of bytes read."452
442/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile453 // Try to queue the 1-byte read.
443fn windowsGetReadResult(454 if (0 == windows.kernel32.ReadFile(
444 handle: windows.HANDLE,455 handle,
445 overlapped: *windows.OVERLAPPED,456 small_buf,
446 allow_aborted: bool,457 small_buf.len,
447) !union(enum) {458 &win_dummy_bytes_read,
448 success: u32,459 overlapped,
449 closed,460 )) switch (windows.GetLastError()) {
450 aborted,461 .IO_PENDING => {
451} {462 // 1-byte read pending as intended
452 var num_bytes_read: u32 = undefined;463 return if (read_any_data) .populated else .empty;
453 if (0 == windows.kernel32.GetOverlappedResult(464 },
454 handle,465 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
455 overlapped,466 else => |err| return windows.unexpectedError(err),
456 &num_bytes_read,467 };
457 0,468
458 )) switch (windows.GetLastError()) {469 // We got data back this time. Write it to the FIFO and run the main loop again.
459 .BROKEN_PIPE => return .closed,470 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
460 .OPERATION_ABORTED => |err| if (allow_aborted) {471 .success => |n| n,
461 return .aborted;472 .closed => return if (read_any_data) .closed_populated else .closed,
462 } else {473 .aborted => unreachable,
463 return windows.unexpectedError(err);474 };
464 },475 const buf = small_buf[0..num_bytes_read];
465 else => |err| return windows.unexpectedError(err),476 const dest = try writableSliceGreedyAlloc(r, gpa, buf.len);
477 @memcpy(dest[0..buf.len], buf);
478 advanceBufferEnd(r, buf.len);
479 read_any_data = true;
480 }
481 }
482
483 /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
484 /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
485 ///
486 /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
487 /// operation immediately returns data:
488 /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
489 /// erroneous results."
490 /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
491 /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
492 /// get the actual number of bytes read."
493 /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
494 fn windowsGetReadResult(
495 handle: windows.HANDLE,
496 overlapped: *windows.OVERLAPPED,
497 allow_aborted: bool,
498 ) !union(enum) {
499 success: u32,
500 closed,
501 aborted,
502 } {
503 var num_bytes_read: u32 = undefined;
504 if (0 == windows.kernel32.GetOverlappedResult(
505 handle,
506 overlapped,
507 &num_bytes_read,
508 0,
509 )) switch (windows.GetLastError()) {
510 .BROKEN_PIPE => return .closed,
511 .OPERATION_ABORTED => |err| if (allow_aborted) {
512 return .aborted;
513 } else {
514 return windows.unexpectedError(err);
515 },
516 else => |err| return windows.unexpectedError(err),
517 };
518 return .{ .success = num_bytes_read };
519 }
466 };520 };
467 return .{ .success = num_bytes_read };
468}521}
469522
470/// Given an enum, returns a struct with fields of that enum, each field523/// Given an enum, returns a struct with fields of that enum, each field
lib/std/Io/Reader.zig-31
...@@ -1241,37 +1241,6 @@ pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void...@@ -1241,37 +1241,6 @@ pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void
1241 return fill(r, n);1241 return fill(r, n);
1242}1242}
12431243
1244/// Returns a slice into the unused capacity of `buffer` with at least
1245/// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
1246///
1247/// After calling this function, typically the caller will follow up with a
1248/// call to `advanceBufferEnd` to report the actual number of bytes buffered.
1249pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
1250 {
1251 const unused = r.buffer[r.end..];
1252 if (unused.len >= min_len) return unused;
1253 }
1254 if (r.seek > 0) rebase(r);
1255 {
1256 var list: ArrayList(u8) = .{
1257 .items = r.buffer[0..r.end],
1258 .capacity = r.buffer.len,
1259 };
1260 defer r.buffer = list.allocatedSlice();
1261 try list.ensureUnusedCapacity(allocator, min_len);
1262 }
1263 const unused = r.buffer[r.end..];
1264 assert(unused.len >= min_len);
1265 return unused;
1266}
1267
1268/// After writing directly into the unused capacity of `buffer`, this function
1269/// updates `end` so that users of `Reader` can receive the data.
1270pub fn advanceBufferEnd(r: *Reader, n: usize) void {
1271 assert(n <= r.buffer.len - r.end);
1272 r.end += n;
1273}
1274
1275fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {1244fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1276 const result_info = @typeInfo(Result).int;1245 const result_info = @typeInfo(Result).int;
1277 comptime assert(result_info.bits % 7 == 0);1246 comptime assert(result_info.bits % 7 == 0);
lib/std/c.zig+1-1
...@@ -7147,7 +7147,7 @@ pub const dirent = switch (native_os) {...@@ -7147,7 +7147,7 @@ pub const dirent = switch (native_os) {
7147 off: off_t,7147 off: off_t,
7148 reclen: c_ushort,7148 reclen: c_ushort,
7149 type: u8,7149 type: u8,
7150 name: [256:0]u8,7150 name: [255:0]u8,
7151 },7151 },
7152 else => void,7152 else => void,
7153};7153};
lib/std/posix.zig+21-4
...@@ -192,10 +192,27 @@ pub const iovec_const = extern struct {...@@ -192,10 +192,27 @@ pub const iovec_const = extern struct {
192 len: usize,192 len: usize,
193};193};
194194
195pub const ACCMODE = enum(u2) {195pub const ACCMODE = switch (native_os) {
196 RDONLY = 0,196 // POSIX has a note about the access mode values:
197 WRONLY = 1,197 //
198 RDWR = 2,198 // In historical implementations the value of O_RDONLY is zero. Because of
199 // that, it is not possible to detect the presence of O_RDONLY and another
200 // option. Future implementations should encode O_RDONLY and O_WRONLY as
201 // bit flags so that: O_RDONLY | O_WRONLY == O_RDWR
202 //
203 // In practice SerenityOS is the only system supported by Zig that
204 // implements this suggestion.
205 // https://github.com/SerenityOS/serenity/blob/4adc51fdf6af7d50679c48b39362e062f5a3b2cb/Kernel/API/POSIX/fcntl.h#L28-L30
206 .serenity => enum(u2) {
207 RDONLY = 1,
208 WRONLY = 2,
209 RDWR = 3,
210 },
211 else => enum(u2) {
212 RDONLY = 0,
213 WRONLY = 1,
214 RDWR = 2,
215 },
199};216};
200217
201pub const TCSA = enum(c_uint) {218pub const TCSA = enum(c_uint) {
lib/std/process/Child.zig+38-29
...@@ -14,6 +14,7 @@ const assert = std.debug.assert;...@@ -14,6 +14,7 @@ const assert = std.debug.assert;
14const native_os = builtin.os.tag;14const native_os = builtin.os.tag;
15const Allocator = std.mem.Allocator;15const Allocator = std.mem.Allocator;
16const ChildProcess = @This();16const ChildProcess = @This();
17const ArrayList = std.ArrayListUnmanaged;
1718
18pub const Id = switch (native_os) {19pub const Id = switch (native_os) {
19 .windows => windows.HANDLE,20 .windows => windows.HANDLE,
...@@ -348,18 +349,6 @@ pub const RunResult = struct {...@@ -348,18 +349,6 @@ pub const RunResult = struct {
348 stderr: []u8,349 stderr: []u8,
349};350};
350351
351fn writeBufferedReaderToArrayList(allocator: Allocator, list: *std.ArrayListUnmanaged(u8), r: *std.Io.Reader) !void {
352 assert(r.seek == 0);
353 if (list.capacity == 0) {
354 list.* = .{
355 .items = r.buffered(),
356 .capacity = r.buffer.len,
357 };
358 } else {
359 try list.appendSlice(allocator, r.buffered());
360 }
361}
362
363/// Collect the output from the process's stdout and stderr. Will return once all output352/// Collect the output from the process's stdout and stderr. Will return once all output
364/// has been collected. This does not mean that the process has ended. `wait` should still353/// has been collected. This does not mean that the process has ended. `wait` should still
365/// be called to wait for and clean up the process.354/// be called to wait for and clean up the process.
...@@ -369,8 +358,8 @@ pub fn collectOutput(...@@ -369,8 +358,8 @@ pub fn collectOutput(
369 child: ChildProcess,358 child: ChildProcess,
370 /// Used for `stdout` and `stderr`.359 /// Used for `stdout` and `stderr`.
371 allocator: Allocator,360 allocator: Allocator,
372 stdout: *std.ArrayListUnmanaged(u8),361 stdout: *ArrayList(u8),
373 stderr: *std.ArrayListUnmanaged(u8),362 stderr: *ArrayList(u8),
374 max_output_bytes: usize,363 max_output_bytes: usize,
375) !void {364) !void {
376 assert(child.stdout_behavior == .Pipe);365 assert(child.stdout_behavior == .Pipe);
...@@ -382,15 +371,35 @@ pub fn collectOutput(...@@ -382,15 +371,35 @@ pub fn collectOutput(
382 });371 });
383 defer poller.deinit();372 defer poller.deinit();
384373
374 const stdout_r = poller.reader(.stdout);
375 stdout_r.buffer = stdout.allocatedSlice();
376 stdout_r.seek = 0;
377 stdout_r.end = stdout.items.len;
378
379 const stderr_r = poller.reader(.stderr);
380 stderr_r.buffer = stderr.allocatedSlice();
381 stderr_r.seek = 0;
382 stderr_r.end = stderr.items.len;
383
384 defer {
385 stdout.* = .{
386 .items = stdout_r.buffer[0..stdout_r.end],
387 .capacity = stdout_r.buffer.len,
388 };
389 stderr.* = .{
390 .items = stderr_r.buffer[0..stderr_r.end],
391 .capacity = stderr_r.buffer.len,
392 };
393 stdout_r.buffer = &.{};
394 stderr_r.buffer = &.{};
395 }
396
385 while (try poller.poll()) {397 while (try poller.poll()) {
386 if (poller.reader(.stdout).bufferedLen() > max_output_bytes)398 if (stdout_r.bufferedLen() > max_output_bytes)
387 return error.StdoutStreamTooLong;399 return error.StdoutStreamTooLong;
388 if (poller.reader(.stderr).bufferedLen() > max_output_bytes)400 if (stderr_r.bufferedLen() > max_output_bytes)
389 return error.StderrStreamTooLong;401 return error.StderrStreamTooLong;
390 }402 }
391
392 try writeBufferedReaderToArrayList(allocator, stdout, poller.reader(.stdout));
393 try writeBufferedReaderToArrayList(allocator, stderr, poller.reader(.stderr));
394}403}
395404
396pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{405pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{
...@@ -420,10 +429,10 @@ pub fn run(args: struct {...@@ -420,10 +429,10 @@ pub fn run(args: struct {
420 child.expand_arg0 = args.expand_arg0;429 child.expand_arg0 = args.expand_arg0;
421 child.progress_node = args.progress_node;430 child.progress_node = args.progress_node;
422431
423 var stdout: std.ArrayListUnmanaged(u8) = .empty;432 var stdout: ArrayList(u8) = .empty;
424 errdefer stdout.deinit(args.allocator);433 defer stdout.deinit(args.allocator);
425 var stderr: std.ArrayListUnmanaged(u8) = .empty;434 var stderr: ArrayList(u8) = .empty;
426 errdefer stderr.deinit(args.allocator);435 defer stderr.deinit(args.allocator);
427436
428 try child.spawn();437 try child.spawn();
429 errdefer {438 errdefer {
...@@ -431,7 +440,7 @@ pub fn run(args: struct {...@@ -431,7 +440,7 @@ pub fn run(args: struct {
431 }440 }
432 try child.collectOutput(args.allocator, &stdout, &stderr, args.max_output_bytes);441 try child.collectOutput(args.allocator, &stdout, &stderr, args.max_output_bytes);
433442
434 return RunResult{443 return .{
435 .stdout = try stdout.toOwnedSlice(args.allocator),444 .stdout = try stdout.toOwnedSlice(args.allocator),
436 .stderr = try stderr.toOwnedSlice(args.allocator),445 .stderr = try stderr.toOwnedSlice(args.allocator),
437 .term = try child.wait(),446 .term = try child.wait(),
...@@ -877,12 +886,12 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {...@@ -877,12 +886,12 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void {
877 var cmd_line_cache = WindowsCommandLineCache.init(self.allocator, self.argv);886 var cmd_line_cache = WindowsCommandLineCache.init(self.allocator, self.argv);
878 defer cmd_line_cache.deinit();887 defer cmd_line_cache.deinit();
879888
880 var app_buf: std.ArrayListUnmanaged(u16) = .empty;889 var app_buf: ArrayList(u16) = .empty;
881 defer app_buf.deinit(self.allocator);890 defer app_buf.deinit(self.allocator);
882891
883 try app_buf.appendSlice(self.allocator, app_name_w);892 try app_buf.appendSlice(self.allocator, app_name_w);
884893
885 var dir_buf: std.ArrayListUnmanaged(u16) = .empty;894 var dir_buf: ArrayList(u16) = .empty;
886 defer dir_buf.deinit(self.allocator);895 defer dir_buf.deinit(self.allocator);
887896
888 if (cwd_path_w.len > 0) {897 if (cwd_path_w.len > 0) {
...@@ -1022,8 +1031,8 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);...@@ -1022,8 +1031,8 @@ const ErrInt = std.meta.Int(.unsigned, @sizeOf(anyerror) * 8);
1022/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).1031/// Note: If the dir is the cwd, dir_buf should be empty (len = 0).
1023fn windowsCreateProcessPathExt(1032fn windowsCreateProcessPathExt(
1024 allocator: mem.Allocator,1033 allocator: mem.Allocator,
1025 dir_buf: *std.ArrayListUnmanaged(u16),1034 dir_buf: *ArrayList(u16),
1026 app_buf: *std.ArrayListUnmanaged(u16),1035 app_buf: *ArrayList(u16),
1027 pathext: [:0]const u16,1036 pathext: [:0]const u16,
1028 cmd_line_cache: *WindowsCommandLineCache,1037 cmd_line_cache: *WindowsCommandLineCache,
1029 envp_ptr: ?[*]u16,1038 envp_ptr: ?[*]u16,
...@@ -1506,7 +1515,7 @@ const WindowsCommandLineCache = struct {...@@ -1506,7 +1515,7 @@ const WindowsCommandLineCache = struct {
1506/// Returns the absolute path of `cmd.exe` within the Windows system directory.1515/// Returns the absolute path of `cmd.exe` within the Windows system directory.
1507/// The caller owns the returned slice.1516/// The caller owns the returned slice.
1508fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {1517fn windowsCmdExePath(allocator: mem.Allocator) error{ OutOfMemory, Unexpected }![:0]u16 {
1509 var buf = try std.ArrayListUnmanaged(u16).initCapacity(allocator, 128);1518 var buf = try ArrayList(u16).initCapacity(allocator, 128);
1510 errdefer buf.deinit(allocator);1519 errdefer buf.deinit(allocator);
1511 while (true) {1520 while (true) {
1512 const unused_slice = buf.unusedCapacitySlice();1521 const unused_slice = buf.unusedCapacitySlice();
src/Compilation.zig+11-13
...@@ -6215,19 +6215,20 @@ fn spawnZigRc(...@@ -6215,19 +6215,20 @@ fn spawnZigRc(
6215 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });6215 return comp.failWin32Resource(win32_resource, "unable to spawn {s} rc: {s}", .{ argv[0], @errorName(err) });
6216 };6216 };
62176217
6218 var poller = std.io.poll(comp.gpa, enum { stdout }, .{6218 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{
6219 .stdout = child.stdout.?,6219 .stdout = child.stdout.?,
6220 .stderr = child.stderr.?,
6220 });6221 });
6221 defer poller.deinit();6222 defer poller.deinit();
62226223
6223 const stdout = poller.fifo(.stdout);6224 const stdout = poller.reader(.stdout);
62246225
6225 poll: while (true) {6226 poll: while (true) {
6226 while (stdout.readableLength() < @sizeOf(std.zig.Server.Message.Header)) if (!try poller.poll()) break :poll;6227 const MessageHeader = std.zig.Server.Message.Header;
6227 var header: std.zig.Server.Message.Header = undefined;6228 while (stdout.buffered().len < @sizeOf(MessageHeader)) if (!try poller.poll()) break :poll;
6228 assert(stdout.read(std.mem.asBytes(&header)) == @sizeOf(std.zig.Server.Message.Header));6229 const header = stdout.takeStruct(MessageHeader, .little) catch unreachable;
6229 while (stdout.readableLength() < header.bytes_len) if (!try poller.poll()) break :poll;6230 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;
6230 const body = stdout.readableSliceOfLen(header.bytes_len);6231 const body = stdout.take(header.bytes_len) catch unreachable;
62316232
6232 switch (header.tag) {6233 switch (header.tag) {
6233 // We expect exactly one ErrorBundle, and if any error_bundle header is6234 // We expect exactly one ErrorBundle, and if any error_bundle header is
...@@ -6250,13 +6251,10 @@ fn spawnZigRc(...@@ -6250,13 +6251,10 @@ fn spawnZigRc(
6250 },6251 },
6251 else => {}, // ignore other messages6252 else => {}, // ignore other messages
6252 }6253 }
6253
6254 stdout.discard(body.len);
6255 }6254 }
62566255
6257 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)6256 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)
6258 const stderr_reader = child.stderr.?.deprecatedReader();6257 const stderr = poller.reader(.stderr);
6259 const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024);
62606258
6261 const term = child.wait() catch |err| {6259 const term = child.wait() catch |err| {
6262 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });6260 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {s}", .{ argv[0], @errorName(err) });
...@@ -6265,12 +6263,12 @@ fn spawnZigRc(...@@ -6265,12 +6263,12 @@ fn spawnZigRc(
6265 switch (term) {6263 switch (term) {
6266 .Exited => |code| {6264 .Exited => |code| {
6267 if (code != 0) {6265 if (code != 0) {
6268 log.err("zig rc failed with stderr:\n{s}", .{stderr});6266 log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()});
6269 return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});6267 return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});
6270 }6268 }
6271 },6269 },
6272 else => {6270 else => {
6273 log.err("zig rc terminated with stderr:\n{s}", .{stderr});6271 log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()});
6274 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});6272 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
6275 },6273 },
6276 }6274 }
src/arch/wasm/CodeGen.zig+9-12
...@@ -1887,8 +1887,10 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1887,8 +1887,10 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1887 .call_never_tail => cg.airCall(inst, .never_tail),1887 .call_never_tail => cg.airCall(inst, .never_tail),
1888 .call_never_inline => cg.airCall(inst, .never_inline),1888 .call_never_inline => cg.airCall(inst, .never_inline),
18891889
1890 .is_err => cg.airIsErr(inst, .i32_ne),1890 .is_err => cg.airIsErr(inst, .i32_ne, .value),
1891 .is_non_err => cg.airIsErr(inst, .i32_eq),1891 .is_non_err => cg.airIsErr(inst, .i32_eq, .value),
1892 .is_err_ptr => cg.airIsErr(inst, .i32_ne, .ptr),
1893 .is_non_err_ptr => cg.airIsErr(inst, .i32_eq, .ptr),
18921894
1893 .is_null => cg.airIsNull(inst, .i32_eq, .value),1895 .is_null => cg.airIsNull(inst, .i32_eq, .value),
1894 .is_non_null => cg.airIsNull(inst, .i32_ne, .value),1896 .is_non_null => cg.airIsNull(inst, .i32_ne, .value),
...@@ -1971,8 +1973,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -1971,8 +1973,6 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
1971 .runtime_nav_ptr => cg.airRuntimeNavPtr(inst),1973 .runtime_nav_ptr => cg.airRuntimeNavPtr(inst),
19721974
1973 .assembly,1975 .assembly,
1974 .is_err_ptr,
1975 .is_non_err_ptr,
19761976
1977 .err_return_trace,1977 .err_return_trace,
1978 .set_err_return_trace,1978 .set_err_return_trace,
...@@ -4106,7 +4106,7 @@ fn airSwitchDispatch(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {...@@ -4106,7 +4106,7 @@ fn airSwitchDispatch(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
4106 return cg.finishAir(inst, .none, &.{br.operand});4106 return cg.finishAir(inst, .none, &.{br.operand});
4107}4107}
41084108
4109fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerError!void {4109fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode, op_kind: enum { value, ptr }) InnerError!void {
4110 const zcu = cg.pt.zcu;4110 const zcu = cg.pt.zcu;
4111 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;4111 const un_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
4112 const operand = try cg.resolveInst(un_op);4112 const operand = try cg.resolveInst(un_op);
...@@ -4123,7 +4123,7 @@ fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerEr...@@ -4123,7 +4123,7 @@ fn airIsErr(cg: *CodeGen, inst: Air.Inst.Index, opcode: std.wasm.Opcode) InnerEr
4123 }4123 }
41244124
4125 try cg.emitWValue(operand);4125 try cg.emitWValue(operand);
4126 if (pl_ty.hasRuntimeBitsIgnoreComptime(zcu)) {4126 if (op_kind == .ptr or pl_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
4127 try cg.addMemArg(.i32_load16_u, .{4127 try cg.addMemArg(.i32_load16_u, .{
4128 .offset = operand.offset() + @as(u32, @intCast(errUnionErrorOffset(pl_ty, zcu))),4128 .offset = operand.offset() + @as(u32, @intCast(errUnionErrorOffset(pl_ty, zcu))),
4129 .alignment = @intCast(Type.anyerror.abiAlignment(zcu).toByteUnits().?),4129 .alignment = @intCast(Type.anyerror.abiAlignment(zcu).toByteUnits().?),
...@@ -6463,9 +6463,6 @@ fn lowerTry(...@@ -6463,9 +6463,6 @@ fn lowerTry(
6463 operand_is_ptr: bool,6463 operand_is_ptr: bool,
6464) InnerError!WValue {6464) InnerError!WValue {
6465 const zcu = cg.pt.zcu;6465 const zcu = cg.pt.zcu;
6466 if (operand_is_ptr) {
6467 return cg.fail("TODO: lowerTry for pointers", .{});
6468 }
64696466
6470 const pl_ty = err_union_ty.errorUnionPayload(zcu);6467 const pl_ty = err_union_ty.errorUnionPayload(zcu);
6471 const pl_has_bits = pl_ty.hasRuntimeBitsIgnoreComptime(zcu);6468 const pl_has_bits = pl_ty.hasRuntimeBitsIgnoreComptime(zcu);
...@@ -6476,7 +6473,7 @@ fn lowerTry(...@@ -6476,7 +6473,7 @@ fn lowerTry(
64766473
6477 // check if the error tag is set for the error union.6474 // check if the error tag is set for the error union.
6478 try cg.emitWValue(err_union);6475 try cg.emitWValue(err_union);
6479 if (pl_has_bits) {6476 if (pl_has_bits or operand_is_ptr) {
6480 const err_offset: u32 = @intCast(errUnionErrorOffset(pl_ty, zcu));6477 const err_offset: u32 = @intCast(errUnionErrorOffset(pl_ty, zcu));
6481 try cg.addMemArg(.i32_load16_u, .{6478 try cg.addMemArg(.i32_load16_u, .{
6482 .offset = err_union.offset() + err_offset,6479 .offset = err_union.offset() + err_offset,
...@@ -6498,12 +6495,12 @@ fn lowerTry(...@@ -6498,12 +6495,12 @@ fn lowerTry(
6498 }6495 }
64996496
6500 // if we reach here it means error was not set, and we want the payload6497 // if we reach here it means error was not set, and we want the payload
6501 if (!pl_has_bits) {6498 if (!pl_has_bits and !operand_is_ptr) {
6502 return .none;6499 return .none;
6503 }6500 }
65046501
6505 const pl_offset: u32 = @intCast(errUnionPayloadOffset(pl_ty, zcu));6502 const pl_offset: u32 = @intCast(errUnionPayloadOffset(pl_ty, zcu));
6506 if (isByRef(pl_ty, zcu, cg.target)) {6503 if (operand_is_ptr or isByRef(pl_ty, zcu, cg.target)) {
6507 return buildPointerOffset(cg, err_union, pl_offset, .new);6504 return buildPointerOffset(cg, err_union, pl_offset, .new);
6508 }6505 }
6509 const payload = try cg.load(err_union, pl_ty, pl_offset);6506 const payload = try cg.load(err_union, pl_ty, pl_offset);
src/target.zig+2
...@@ -414,6 +414,8 @@ pub fn libcFullLinkFlags(target: *const std.Target) []const []const u8 {...@@ -414,6 +414,8 @@ pub fn libcFullLinkFlags(target: *const std.Target) []const []const u8 {
414 .android, .androideabi, .ohos, .ohoseabi => &.{ "-lm", "-lc", "-ldl" },414 .android, .androideabi, .ohos, .ohoseabi => &.{ "-lm", "-lc", "-ldl" },
415 else => &.{ "-lm", "-lpthread", "-lc", "-ldl", "-lrt", "-lutil" },415 else => &.{ "-lm", "-lpthread", "-lc", "-ldl", "-lrt", "-lutil" },
416 },416 },
417 // On SerenityOS libc includes libm, libpthread, libdl, and libssp.
418 .serenity => &.{"-lc"},
417 else => &.{},419 else => &.{},
418 };420 };
419 return result;421 return result;
test/behavior/try.zig+79
...@@ -121,3 +121,82 @@ test "'return try' through conditional" {...@@ -121,3 +121,82 @@ test "'return try' through conditional" {
121 comptime std.debug.assert(result == 123);121 comptime std.debug.assert(result == 123);
122 }122 }
123}123}
124
125test "try ptr propagation const" {
126 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
127 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
128 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
129 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
130 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
131
132 const S = struct {
133 fn foo0() !u32 {
134 return 0;
135 }
136
137 fn foo1() error{Bad}!u32 {
138 return 1;
139 }
140
141 fn foo2() anyerror!u32 {
142 return 2;
143 }
144
145 fn doTheTest() !void {
146 const res0: *const u32 = &(try foo0());
147 const res1: *const u32 = &(try foo1());
148 const res2: *const u32 = &(try foo2());
149 try expect(res0.* == 0);
150 try expect(res1.* == 1);
151 try expect(res2.* == 2);
152 }
153 };
154 try S.doTheTest();
155 try comptime S.doTheTest();
156}
157
158test "try ptr propagation mutate" {
159 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
160 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
161 if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
162 if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
163 if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
164
165 const S = struct {
166 fn foo0() !u32 {
167 return 0;
168 }
169
170 fn foo1() error{Bad}!u32 {
171 return 1;
172 }
173
174 fn foo2() anyerror!u32 {
175 return 2;
176 }
177
178 fn doTheTest() !void {
179 var f0 = foo0();
180 var f1 = foo1();
181 var f2 = foo2();
182
183 const res0: *u32 = &(try f0);
184 const res1: *u32 = &(try f1);
185 const res2: *u32 = &(try f2);
186
187 res0.* += 1;
188 res1.* += 1;
189 res2.* += 1;
190
191 try expect(f0 catch unreachable == 1);
192 try expect(f1 catch unreachable == 2);
193 try expect(f2 catch unreachable == 3);
194
195 try expect(res0.* == 1);
196 try expect(res1.* == 2);
197 try expect(res2.* == 3);
198 }
199 };
200 try S.doTheTest();
201 try comptime S.doTheTest();
202}
test/src/Cases.zig+2
...@@ -800,6 +800,8 @@ const TestManifestConfigDefaults = struct {...@@ -800,6 +800,8 @@ const TestManifestConfigDefaults = struct {
800 }800 }
801 // Windows801 // Windows
802 defaults = defaults ++ "x86_64-windows" ++ ",";802 defaults = defaults ++ "x86_64-windows" ++ ",";
803 // Wasm
804 defaults = defaults ++ "wasm32-wasi";
803 break :blk defaults;805 break :blk defaults;
804 };806 };
805 } else if (std.mem.eql(u8, key, "output_mode")) {807 } else if (std.mem.eql(u8, key, "output_mode")) {
test/tests.zig+9-10
...@@ -1369,16 +1369,15 @@ const test_targets = blk: {...@@ -1369,16 +1369,15 @@ const test_targets = blk: {
13691369
1370 // WASI Targets1370 // WASI Targets
13711371
1372 // TODO: lowerTry for pointers1372 .{
1373 //.{1373 .target = .{
1374 // .target = .{1374 .cpu_arch = .wasm32,
1375 // .cpu_arch = .wasm32,1375 .os_tag = .wasi,
1376 // .os_tag = .wasi,1376 .abi = .none,
1377 // .abi = .none,1377 },
1378 // },1378 .use_llvm = false,
1379 // .use_llvm = false,1379 .use_lld = false,
1380 // .use_lld = false,1380 },
1381 //},
1382 .{1381 .{
1383 .target = .{1382 .target = .{
1384 .cpu_arch = .wasm32,1383 .cpu_arch = .wasm32,
tools/docgen.zig-1
...@@ -3,7 +3,6 @@ const builtin = @import("builtin");...@@ -3,7 +3,6 @@ const builtin = @import("builtin");
3const io = std.io;3const io = std.io;
4const fs = std.fs;4const fs = std.fs;
5const process = std.process;5const process = std.process;
6const ChildProcess = std.process.Child;
7const Progress = std.Progress;6const Progress = std.Progress;
8const print = std.debug.print;7const print = std.debug.print;
9const mem = std.mem;8const mem = std.mem;
tools/incr-check.zig+24-39
...@@ -186,7 +186,7 @@ pub fn main() !void {...@@ -186,7 +186,7 @@ pub fn main() !void {
186186
187 try child.spawn();187 try child.spawn();
188188
189 var poller = std.io.poll(arena, Eval.StreamEnum, .{189 var poller = std.Io.poll(arena, Eval.StreamEnum, .{
190 .stdout = child.stdout.?,190 .stdout = child.stdout.?,
191 .stderr = child.stderr.?,191 .stderr = child.stderr.?,
192 });192 });
...@@ -247,19 +247,15 @@ const Eval = struct {...@@ -247,19 +247,15 @@ const Eval = struct {
247247
248 fn check(eval: *Eval, poller: *Poller, update: Case.Update, prog_node: std.Progress.Node) !void {248 fn check(eval: *Eval, poller: *Poller, update: Case.Update, prog_node: std.Progress.Node) !void {
249 const arena = eval.arena;249 const arena = eval.arena;
250 const Header = std.zig.Server.Message.Header;250 const stdout = poller.reader(.stdout);
251 const stdout = poller.fifo(.stdout);251 const stderr = poller.reader(.stderr);
252 const stderr = poller.fifo(.stderr);
253252
254 poll: while (true) {253 poll: while (true) {
255 while (stdout.readableLength() < @sizeOf(Header)) {254 const Header = std.zig.Server.Message.Header;
256 if (!(try poller.poll())) break :poll;255 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll;
257 }256 const header = stdout.takeStruct(Header, .little) catch unreachable;
258 const header = stdout.reader().readStruct(Header) catch unreachable;257 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;
259 while (stdout.readableLength() < header.bytes_len) {258 const body = stdout.take(header.bytes_len) catch unreachable;
260 if (!(try poller.poll())) break :poll;
261 }
262 const body = stdout.readableSliceOfLen(header.bytes_len);
263259
264 switch (header.tag) {260 switch (header.tag) {
265 .error_bundle => {261 .error_bundle => {
...@@ -277,8 +273,8 @@ const Eval = struct {...@@ -277,8 +273,8 @@ const Eval = struct {
277 .string_bytes = try arena.dupe(u8, string_bytes),273 .string_bytes = try arena.dupe(u8, string_bytes),
278 .extra = extra_array,274 .extra = extra_array,
279 };275 };
280 if (stderr.readableLength() > 0) {276 if (stderr.bufferedLen() > 0) {
281 const stderr_data = try stderr.toOwnedSlice();277 const stderr_data = try poller.toOwnedSlice(.stderr);
282 if (eval.allow_stderr) {278 if (eval.allow_stderr) {
283 std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});279 std.log.info("error_bundle included stderr:\n{s}", .{stderr_data});
284 } else {280 } else {
...@@ -289,15 +285,14 @@ const Eval = struct {...@@ -289,15 +285,14 @@ const Eval = struct {
289 try eval.checkErrorOutcome(update, result_error_bundle);285 try eval.checkErrorOutcome(update, result_error_bundle);
290 }286 }
291 // This message indicates the end of the update.287 // This message indicates the end of the update.
292 stdout.discard(body.len);
293 return;288 return;
294 },289 },
295 .emit_digest => {290 .emit_digest => {
296 const EbpHdr = std.zig.Server.Message.EmitDigest;291 const EbpHdr = std.zig.Server.Message.EmitDigest;
297 const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));292 const ebp_hdr = @as(*align(1) const EbpHdr, @ptrCast(body));
298 _ = ebp_hdr;293 _ = ebp_hdr;
299 if (stderr.readableLength() > 0) {294 if (stderr.bufferedLen() > 0) {
300 const stderr_data = try stderr.toOwnedSlice();295 const stderr_data = try poller.toOwnedSlice(.stderr);
301 if (eval.allow_stderr) {296 if (eval.allow_stderr) {
302 std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});297 std.log.info("emit_digest included stderr:\n{s}", .{stderr_data});
303 } else {298 } else {
...@@ -308,7 +303,6 @@ const Eval = struct {...@@ -308,7 +303,6 @@ const Eval = struct {
308 if (eval.target.backend == .sema) {303 if (eval.target.backend == .sema) {
309 try eval.checkSuccessOutcome(update, null, prog_node);304 try eval.checkSuccessOutcome(update, null, prog_node);
310 // This message indicates the end of the update.305 // This message indicates the end of the update.
311 stdout.discard(body.len);
312 }306 }
313307
314 const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len];308 const digest = body[@sizeOf(EbpHdr)..][0..Cache.bin_digest_len];
...@@ -323,21 +317,18 @@ const Eval = struct {...@@ -323,21 +317,18 @@ const Eval = struct {
323317
324 try eval.checkSuccessOutcome(update, bin_path, prog_node);318 try eval.checkSuccessOutcome(update, bin_path, prog_node);
325 // This message indicates the end of the update.319 // This message indicates the end of the update.
326 stdout.discard(body.len);
327 },320 },
328 else => {321 else => {
329 // Ignore other messages.322 // Ignore other messages.
330 stdout.discard(body.len);
331 },323 },
332 }324 }
333 }325 }
334326
335 if (stderr.readableLength() > 0) {327 if (stderr.bufferedLen() > 0) {
336 const stderr_data = try stderr.toOwnedSlice();
337 if (eval.allow_stderr) {328 if (eval.allow_stderr) {
338 std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr_data });329 std.log.info("update '{s}' included stderr:\n{s}", .{ update.name, stderr.buffered() });
339 } else {330 } else {
340 eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr_data });331 eval.fatal("update '{s}' failed:\n{s}", .{ update.name, stderr.buffered() });
341 }332 }
342 }333 }
343334
...@@ -537,25 +528,19 @@ const Eval = struct {...@@ -537,25 +528,19 @@ const Eval = struct {
537 fn end(eval: *Eval, poller: *Poller) !void {528 fn end(eval: *Eval, poller: *Poller) !void {
538 requestExit(eval.child, eval);529 requestExit(eval.child, eval);
539530
540 const Header = std.zig.Server.Message.Header;531 const stdout = poller.reader(.stdout);
541 const stdout = poller.fifo(.stdout);532 const stderr = poller.reader(.stderr);
542 const stderr = poller.fifo(.stderr);
543533
544 poll: while (true) {534 poll: while (true) {
545 while (stdout.readableLength() < @sizeOf(Header)) {535 const Header = std.zig.Server.Message.Header;
546 if (!(try poller.poll())) break :poll;536 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll;
547 }537 const header = stdout.takeStruct(Header, .little) catch unreachable;
548 const header = stdout.reader().readStruct(Header) catch unreachable;538 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;
549 while (stdout.readableLength() < header.bytes_len) {539 stdout.toss(header.bytes_len);
550 if (!(try poller.poll())) break :poll;
551 }
552 const body = stdout.readableSliceOfLen(header.bytes_len);
553 stdout.discard(body.len);
554 }540 }
555541
556 if (stderr.readableLength() > 0) {542 if (stderr.bufferedLen() > 0) {
557 const stderr_data = try stderr.toOwnedSlice();543 eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()});
558 eval.fatal("unexpected stderr:\n{s}", .{stderr_data});
559 }544 }
560 }545 }
561546