authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-15 01:00:42-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:25-07:00
logc2fc6b0b6cc0d5fa6eb6134ac16ba63c9a0059c4
treeae6226b75f2466fd9fc37c14a527f9468099cde4
parent00c6c836a66db0bc08309534a49d4c5941a416aa

ArrayListWriter


8 files changed, 164 insertions(+), 86 deletions(-)

lib/std/Build.zig+7-8
...@@ -1824,13 +1824,13 @@ pub fn validateUserInputDidItFail(b: *Build) bool {...@@ -1824,13 +1824,13 @@ pub fn validateUserInputDidItFail(b: *Build) bool {
1824 return b.invalid_user_input;1824 return b.invalid_user_input;
1825}1825}
18261826
1827fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) error{OutOfMemory}![]u8 {1827fn allocPrintCmd(gpa: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) error{OutOfMemory}![]u8 {
1828 var buf = ArrayList(u8).init(ally);1828 var buf: std.ArrayListUnmanaged(u8) = .empty;
1829 if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});1829 if (opt_cwd) |cwd| try buf.print(gpa, "cd {s} && ", .{cwd});
1830 for (argv) |arg| {1830 for (argv) |arg| {
1831 try buf.writer().print("{s} ", .{arg});1831 try buf.print(gpa, "{s} ", .{arg});
1832 }1832 }
1833 return buf.toOwnedSlice();1833 return buf.toOwnedSlice(gpa);
1834}1834}
18351835
1836fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {1836fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
...@@ -2766,11 +2766,10 @@ fn dumpBadDirnameHelp(...@@ -2766,11 +2766,10 @@ fn dumpBadDirnameHelp(
2766 comptime msg: []const u8,2766 comptime msg: []const u8,
2767 args: anytype,2767 args: anytype,
2768) anyerror!void {2768) anyerror!void {
2769 debug.lockStdErr();2769 var w = debug.lockStdErr2();
2770 defer debug.unlockStdErr();2770 defer debug.unlockStdErr();
27712771
2772 const stderr = io.getStdErr();2772 const stderr = io.getStdErr();
2773 const w = stderr.writer();
2774 try w.print(msg, args);2773 try w.print(msg, args);
27752774
2776 const tty_config = std.io.tty.detectConfig(stderr);2775 const tty_config = std.io.tty.detectConfig(stderr);
...@@ -2803,7 +2802,7 @@ pub fn dumpBadGetPathHelp(...@@ -2803,7 +2802,7 @@ pub fn dumpBadGetPathHelp(
2803 src_builder: *Build,2802 src_builder: *Build,
2804 asking_step: ?*Step,2803 asking_step: ?*Step,
2805) anyerror!void {2804) anyerror!void {
2806 const w = stderr.writer();2805 var w = stderr.unbufferedWriter();
2807 try w.print(2806 try w.print(
2808 \\getPath() was called on a GeneratedFile that wasn't built yet.2807 \\getPath() was called on a GeneratedFile that wasn't built yet.
2809 \\ source package path: {s}2808 \\ source package path: {s}
lib/std/array_list.zig+9
...@@ -1001,6 +1001,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig...@@ -1001,6 +1001,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
1001 return m.len;1001 return m.len;
1002 }1002 }
10031003
1004 pub fn print(self: *Self, gpa: Allocator, comptime fmt: []const u8, args: anytype) error{OutOfMemory}!void {
1005 comptime assert(T == u8);
1006 try self.ensureUnusedCapacity(gpa, fmt.len);
1007 var alw: std.io.ArrayListWriter = undefined;
1008 const bw = alw.fromOwned(gpa, self);
1009 defer self.* = alw.toOwned();
1010 bw.print(fmt, args) catch return error.OutOfMemory;
1011 }
1012
1004 pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWriteFixed);1013 pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWriteFixed);
10051014
1006 /// Initializes a Writer which will append to the list but will return1015 /// Initializes a Writer which will append to the list but will return
lib/std/crypto/tls/Client.zig+1-3
...@@ -95,11 +95,9 @@ pub const StreamInterface = struct {...@@ -95,11 +95,9 @@ pub const StreamInterface = struct {
95 @panic("unimplemented");95 @panic("unimplemented");
96 }96 }
9797
98 /// Returns the number of bytes read, which may be less than the buffer
99 /// space provided, indicating end-of-stream.
100 /// The `iovecs` parameter is mutable in case this function needs to mutate98 /// The `iovecs` parameter is mutable in case this function needs to mutate
101 /// the fields in order to handle partial writes from the underlying layer.99 /// the fields in order to handle partial writes from the underlying layer.
102 pub fn writevAll(this: @This(), iovecs: []std.posix.iovec_const) WriteError!usize {100 pub fn writevAll(this: @This(), iovecs: []std.posix.iovec_const) WriteError!void {
103 // This can be implemented in terms of writev, or specialized if desired.101 // This can be implemented in terms of writev, or specialized if desired.
104 _ = .{ this, iovecs };102 _ = .{ this, iovecs };
105 @panic("unimplemented");103 @panic("unimplemented");
lib/std/debug.zig+3-2
...@@ -1669,7 +1669,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1669,7 +1669,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1669 if (!enabled) return;1669 if (!enabled) return;
16701670
1671 const tty_config = io.tty.detectConfig(std.io.getStdErr());1671 const tty_config = io.tty.detectConfig(std.io.getStdErr());
1672 const stderr = io.getStdErr().writer();1672 var stderr = lockStdErr2();
1673 defer unlockStdErr();
1673 const end = @min(t.index, size);1674 const end = @min(t.index, size);
1674 const debug_info = getSelfDebugInfo() catch |err| {1675 const debug_info = getSelfDebugInfo() catch |err| {
1675 stderr.print(1676 stderr.print(
...@@ -1686,7 +1687,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize...@@ -1686,7 +1687,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
1686 .index = frames.len,1687 .index = frames.len,
1687 .instruction_addresses = frames,1688 .instruction_addresses = frames,
1688 };1689 };
1689 writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue;1690 writeStackTrace(stack_trace, &stderr, debug_info, tty_config) catch continue;
1690 }1691 }
1691 if (t.index > end) {1692 if (t.index > end) {
1692 stderr.print("{d} more traces not shown; consider increasing trace size\n", .{1693 stderr.print("{d} more traces not shown; consider increasing trace size\n", .{
lib/std/fs/File.zig+4-1
...@@ -1621,7 +1621,10 @@ const interface = struct {...@@ -1621,7 +1621,10 @@ const interface = struct {
16211621
1622 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;1622 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1623 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)];1623 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)];
1624 for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{ .base = d.ptr, .len = d.len };1624 for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{
1625 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1626 .len = d.len,
1627 };
1625 return std.posix.writev(file, iovecs);1628 return std.posix.writev(file, iovecs);
1626 }1629 }
16271630
lib/std/io.zig+2-61
...@@ -289,67 +289,6 @@ pub fn GenericReader(...@@ -289,67 +289,6 @@ pub fn GenericReader(
289 };289 };
290}290}
291291
292pub fn GenericWriter(
293 comptime Context: type,
294 comptime WriteError: type,
295 comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize,
296) type {
297 return struct {
298 context: Context,
299
300 const Self = @This();
301 pub const Error = WriteError;
302
303 pub inline fn write(self: Self, bytes: []const u8) Error!usize {
304 return writeFn(self.context, bytes);
305 }
306
307 pub inline fn writeAll(self: Self, bytes: []const u8) Error!void {
308 return @errorCast(self.any().writeAll(bytes));
309 }
310
311 pub inline fn print(self: Self, comptime format: []const u8, args: anytype) Error!void {
312 return @errorCast(self.any().print(format, args));
313 }
314
315 pub inline fn writeByte(self: Self, byte: u8) Error!void {
316 return @errorCast(self.any().writeByte(byte));
317 }
318
319 pub inline fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void {
320 return @errorCast(self.any().writeByteNTimes(byte, n));
321 }
322
323 pub inline fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) Error!void {
324 return @errorCast(self.any().writeBytesNTimes(bytes, n));
325 }
326
327 pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
328 return @errorCast(self.any().writeInt(T, value, endian));
329 }
330
331 pub inline fn writeStruct(self: Self, value: anytype) Error!void {
332 return @errorCast(self.any().writeStruct(value));
333 }
334
335 pub inline fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) Error!void {
336 return @errorCast(self.any().writeStructEndian(value, endian));
337 }
338
339 pub inline fn any(self: *const Self) Writer {
340 return .{
341 .context = @ptrCast(&self.context),
342 .writeFn = typeErasedWriteFn,
343 };
344 }
345
346 fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize {
347 const ptr: *const Context = @alignCast(@ptrCast(context));
348 return writeFn(ptr.*, bytes);
349 }
350 };
351}
352
353/// Deprecated; consider switching to `AnyReader` or use `GenericReader`292/// Deprecated; consider switching to `AnyReader` or use `GenericReader`
354/// to use previous API. To be removed after 0.14.0 is tagged.293/// to use previous API. To be removed after 0.14.0 is tagged.
355pub const Reader = GenericReader;294pub const Reader = GenericReader;
...@@ -362,6 +301,7 @@ pub const AnyWriter = Writer;...@@ -362,6 +301,7 @@ pub const AnyWriter = Writer;
362pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;301pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
363302
364pub const BufferedWriter = @import("io/BufferedWriter.zig");303pub const BufferedWriter = @import("io/BufferedWriter.zig");
304pub const ArrayListWriter = @import("io/ArrayListWriter.zig");
365305
366pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader;306pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader;
367pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader;307pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader;
...@@ -844,6 +784,7 @@ test {...@@ -844,6 +784,7 @@ test {
844 _ = Writer;784 _ = Writer;
845 _ = CountingWriter;785 _ = CountingWriter;
846 _ = FixedBufferStream;786 _ = FixedBufferStream;
787 _ = ArrayListWriter;
847 _ = @import("io/bit_reader.zig");788 _ = @import("io/bit_reader.zig");
848 _ = @import("io/bit_writer.zig");789 _ = @import("io/bit_writer.zig");
849 _ = @import("io/buffered_atomic_file.zig");790 _ = @import("io/buffered_atomic_file.zig");
lib/std/io/ArrayListWriter.zig created+127
...@@ -0,0 +1,127 @@
1//! The straightforward way to use `std.ArrayList` as the underlying writer
2//! when using `std.io.BufferedWriter` is to populate the `std.io.Writer`
3//! interface and then use an empty buffer. However, this means that every use
4//! of `std.io.BufferedWriter` will go through the vtable, including for
5//! functions such as `writeByte`. This API instead maintains
6//! `std.io.BufferedWriter` state such that it writes to the unused capacity of
7//! the array list, filling it up completely before making a call through the
8//! vtable, causing a resize. Consequently, the same, optimized, non-generic
9//! machine code that uses `std.io.BufferedReader`, such as formatted printing,
10//! is also used when the underlying writer is backed by `std.ArrayList`.
11
12const std = @import("../std.zig");
13const ArrayListWriter = @This();
14const assert = std.debug.assert;
15
16items: []u8,
17allocator: std.mem.Allocator,
18buffered_writer: std.io.BufferedWriter,
19
20/// Replaces `array_list` with empty, taking ownership of the memory.
21pub fn fromOwned(
22 alw: *ArrayListWriter,
23 allocator: std.mem.Allocator,
24 array_list: *std.ArrayListUnmanaged(u8),
25) *std.io.BufferedWriter {
26 alw.* = .{
27 .allocated_slice = array_list.items,
28 .allocator = allocator,
29 .buffered_writer = .{
30 .unbuffered_writer = .{
31 .context = alw,
32 .vtable = &.{
33 .writev = writev,
34 .writeFile = writeFile,
35 },
36 },
37 .buffer = array_list.unusedCapacitySlice(),
38 },
39 };
40 array_list.* = .empty;
41 return &alw.buffered_writer;
42}
43
44/// Returns the memory back that was borrowed with `fromOwned`.
45pub fn toOwned(alw: *ArrayListWriter) std.ArrayListUnmanaged(u8) {
46 const end = alw.buffered_writer.end;
47 const result: std.ArrayListUnmanaged(u8) = .{
48 .items = alw.items.ptr[0 .. alw.items.len + end],
49 .capacity = alw.buffered_writer.buffer.len - end,
50 };
51 alw.* = undefined;
52 return result;
53}
54
55fn writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
56 const alw: *ArrayListWriter = @alignCast(@ptrCast(context));
57 const start_len = alw.items.len;
58 const bw = &alw.buffered_writer;
59 assert(data[0].ptr == alw.items.ptr + start_len);
60 const bw_end = data[0].len;
61 var list: std.ArrayListUnmanaged(u8) = .{
62 .items = alw.items.ptr[0 .. start_len + bw_end],
63 .capacity = bw.buffer.len - bw_end,
64 };
65 const rest = data[1..];
66 var new_capacity: usize = list.capacity;
67 for (rest) |bytes| new_capacity += bytes.len;
68 try list.ensureTotalCapacity(alw.allocator, new_capacity + 1);
69 for (rest) |bytes| list.appendSliceAssumeCapacity(bytes);
70 alw.items = list.items;
71 bw.buffer = list.unusedCapacitySlice();
72 return list.items.len - start_len;
73}
74
75fn writeFile(
76 context: *anyopaque,
77 file: std.fs.File,
78 offset: u64,
79 len: std.io.Writer.VTable.FileLen,
80 headers_and_trailers_full: []const []const u8,
81 headers_len_full: usize,
82) anyerror!usize {
83 const alw: *ArrayListWriter = @alignCast(@ptrCast(context));
84 const list = alw.array_list;
85 const bw = &alw.buffered_writer;
86 const start_len = list.items.len;
87 const headers_and_trailers, const headers_len = if (headers_len_full >= 1) b: {
88 assert(headers_and_trailers_full[0].ptr == list.items.ptr + start_len);
89 list.items.len += headers_and_trailers_full[0].len;
90 break :b .{ headers_and_trailers_full[1..], headers_len_full - 1 };
91 } else .{ headers_and_trailers_full, headers_len_full };
92 const gpa = alw.allocator;
93 const trailers = headers_and_trailers[headers_len..];
94 if (len == .entire_file) {
95 var new_capacity: usize = list.capacity + std.atomic.cache_line;
96 for (headers_and_trailers) |bytes| new_capacity += bytes.len;
97 try list.ensureTotalCapacity(gpa, new_capacity);
98 for (headers_and_trailers[0..headers_len]) |bytes| list.appendSliceAssumeCapacity(bytes);
99 const dest = list.items.ptr[list.items.len..list.capacity];
100 const n = try file.pread(dest, offset);
101 if (n == 0) {
102 new_capacity = list.capacity;
103 for (trailers) |bytes| new_capacity += bytes.len;
104 try list.ensureTotalCapacity(gpa, new_capacity);
105 for (trailers) |bytes| list.appendSliceAssumeCapacity(bytes);
106 bw.buffer = list.unusedCapacitySlice();
107 return list.items.len - start_len;
108 }
109 list.items.len += n;
110 bw.buffer = list.unusedCapacitySlice();
111 return list.items.len - start_len;
112 }
113 var new_capacity: usize = list.capacity + len.int();
114 for (headers_and_trailers) |bytes| new_capacity += bytes.len;
115 try list.ensureTotalCapacity(gpa, new_capacity);
116 for (headers_and_trailers[0..headers_len]) |bytes| list.appendSliceAssumeCapacity(bytes);
117 const dest = list.items.ptr[list.items.len..][0..len.int()];
118 const n = try file.pread(dest, offset);
119 list.items.len += n;
120 if (n < dest.len) {
121 bw.buffer = list.unusedCapacitySlice();
122 return list.items.len - start_len;
123 }
124 for (trailers) |bytes| list.appendSliceAssumeCapacity(bytes);
125 bw.buffer = list.unusedCapacitySlice();
126 return list.items.len - start_len;
127}
lib/std/io/BufferedWriter.zig+11-11
...@@ -19,23 +19,21 @@ end: usize = 0,...@@ -19,23 +19,21 @@ end: usize = 0,
19/// vectors through the underlying write calls as possible.19/// vectors through the underlying write calls as possible.
20pub const max_buffers_len = 8;20pub const max_buffers_len = 8;
2121
22const passthru_vtable: Writer.VTable = .{
23 .writev = passthru_writev,
24 .writeFile = passthru_writeFile,
25};
26
27const fixed_vtable: Writer.VTable = .{
28 .writev = fixed_writev,
29 .writeFile = fixed_writeFile,
30};
31
32pub fn writer(bw: *BufferedWriter) Writer {22pub fn writer(bw: *BufferedWriter) Writer {
33 return .{23 return .{
34 .context = bw,24 .context = bw,
35 .vtable = &passthru_vtable,25 .vtable = &.{
26 .writev = passthru_writev,
27 .writeFile = passthru_writeFile,
28 },
36 };29 };
37}30}
3831
32const fixed_vtable: Writer.VTable = .{
33 .writev = fixed_writev,
34 .writeFile = fixed_writeFile,
35};
36
39/// Replaces the `BufferedWriter` with a new one that writes to `buffer` and37/// Replaces the `BufferedWriter` with a new one that writes to `buffer` and
40/// returns `error.NoSpaceLeft` when it is full.38/// returns `error.NoSpaceLeft` when it is full.
41pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void {39pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void {
...@@ -97,6 +95,7 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize...@@ -97,6 +95,7 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize
97 end = new_end;95 end = new_end;
98 continue;96 continue;
99 }97 }
98 if (end == 0) return bw.unbuffered_writer.writev(data);
100 var buffers: [max_buffers_len][]const u8 = undefined;99 var buffers: [max_buffers_len][]const u8 = undefined;
101 buffers[0] = buffer[0..end];100 buffers[0] = buffer[0..end];
102 const remaining_data = data[i..];101 const remaining_data = data[i..];
...@@ -365,6 +364,7 @@ fn passthru_writeFile(...@@ -365,6 +364,7 @@ fn passthru_writeFile(
365) anyerror!usize {364) anyerror!usize {
366 const bw: *BufferedWriter = @alignCast(@ptrCast(context));365 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
367 const buffer = bw.buffer;366 const buffer = bw.buffer;
367 if (buffer.len == 0) return bw.unbuffered_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
368 const start_end = bw.end;368 const start_end = bw.end;
369 const headers = headers_and_trailers[0..headers_len];369 const headers = headers_and_trailers[0..headers_len];
370 const trailers = headers_and_trailers[headers_len..];370 const trailers = headers_and_trailers[headers_len..];