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 {
18241824 return b.invalid_user_input;
18251825}
18261826
1827fn allocPrintCmd(ally: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) error{OutOfMemory}![]u8 {
1828 var buf = ArrayList(u8).init(ally);
1829 if (opt_cwd) |cwd| try buf.writer().print("cd {s} && ", .{cwd});
1827fn allocPrintCmd(gpa: Allocator, opt_cwd: ?[]const u8, argv: []const []const u8) error{OutOfMemory}![]u8 {
1828 var buf: std.ArrayListUnmanaged(u8) = .empty;
1829 if (opt_cwd) |cwd| try buf.print(gpa, "cd {s} && ", .{cwd});
18301830 for (argv) |arg| {
1831 try buf.writer().print("{s} ", .{arg});
1831 try buf.print(gpa, "{s} ", .{arg});
18321832 }
1833 return buf.toOwnedSlice();
1833 return buf.toOwnedSlice(gpa);
18341834}
18351835
18361836fn printCmd(ally: Allocator, cwd: ?[]const u8, argv: []const []const u8) void {
......@@ -2766,11 +2766,10 @@ fn dumpBadDirnameHelp(
27662766 comptime msg: []const u8,
27672767 args: anytype,
27682768) anyerror!void {
2769 debug.lockStdErr();
2769 var w = debug.lockStdErr2();
27702770 defer debug.unlockStdErr();
27712771
27722772 const stderr = io.getStdErr();
2773 const w = stderr.writer();
27742773 try w.print(msg, args);
27752774
27762775 const tty_config = std.io.tty.detectConfig(stderr);
......@@ -2803,7 +2802,7 @@ pub fn dumpBadGetPathHelp(
28032802 src_builder: *Build,
28042803 asking_step: ?*Step,
28052804) anyerror!void {
2806 const w = stderr.writer();
2805 var w = stderr.unbufferedWriter();
28072806 try w.print(
28082807 \\getPath() was called on a GeneratedFile that wasn't built yet.
28092808 \\ source package path: {s}
lib/std/array_list.zig+9
......@@ -1001,6 +1001,15 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?mem.Alig
10011001 return m.len;
10021002 }
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
10041013 pub const FixedWriter = std.io.Writer(*Self, Allocator.Error, appendWriteFixed);
10051014
10061015 /// 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 {
9595 @panic("unimplemented");
9696 }
9797
98 /// Returns the number of bytes read, which may be less than the buffer
99 /// space provided, indicating end-of-stream.
10098 /// The `iovecs` parameter is mutable in case this function needs to mutate
10199 /// 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 {
103101 // This can be implemented in terms of writev, or specialized if desired.
104102 _ = .{ this, iovecs };
105103 @panic("unimplemented");
lib/std/debug.zig+3-2
......@@ -1669,7 +1669,8 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16691669 if (!enabled) return;
16701670
16711671 const tty_config = io.tty.detectConfig(std.io.getStdErr());
1672 const stderr = io.getStdErr().writer();
1672 var stderr = lockStdErr2();
1673 defer unlockStdErr();
16731674 const end = @min(t.index, size);
16741675 const debug_info = getSelfDebugInfo() catch |err| {
16751676 stderr.print(
......@@ -1686,7 +1687,7 @@ pub fn ConfigurableTrace(comptime size: usize, comptime stack_frame_count: usize
16861687 .index = frames.len,
16871688 .instruction_addresses = frames,
16881689 };
1689 writeStackTrace(stack_trace, stderr, debug_info, tty_config) catch continue;
1690 writeStackTrace(stack_trace, &stderr, debug_info, tty_config) catch continue;
16901691 }
16911692 if (t.index > end) {
16921693 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 {
16211621
16221622 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
16231623 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 };
16251628 return std.posix.writev(file, iovecs);
16261629 }
16271630
lib/std/io.zig+2-61
......@@ -289,67 +289,6 @@ pub fn GenericReader(
289289 };
290290}
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
353292/// Deprecated; consider switching to `AnyReader` or use `GenericReader`
354293/// to use previous API. To be removed after 0.14.0 is tagged.
355294pub const Reader = GenericReader;
......@@ -362,6 +301,7 @@ pub const AnyWriter = Writer;
362301pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
363302
364303pub const BufferedWriter = @import("io/BufferedWriter.zig");
304pub const ArrayListWriter = @import("io/ArrayListWriter.zig");
365305
366306pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader;
367307pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader;
......@@ -844,6 +784,7 @@ test {
844784 _ = Writer;
845785 _ = CountingWriter;
846786 _ = FixedBufferStream;
787 _ = ArrayListWriter;
847788 _ = @import("io/bit_reader.zig");
848789 _ = @import("io/bit_writer.zig");
849790 _ = @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,
1919/// vectors through the underlying write calls as possible.
2020pub 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
3222pub fn writer(bw: *BufferedWriter) Writer {
3323 return .{
3424 .context = bw,
35 .vtable = &passthru_vtable,
25 .vtable = &.{
26 .writev = passthru_writev,
27 .writeFile = passthru_writeFile,
28 },
3629 };
3730}
3831
32const fixed_vtable: Writer.VTable = .{
33 .writev = fixed_writev,
34 .writeFile = fixed_writeFile,
35};
36
3937/// Replaces the `BufferedWriter` with a new one that writes to `buffer` and
4038/// returns `error.NoSpaceLeft` when it is full.
4139pub fn initFixed(bw: *BufferedWriter, buffer: []u8) void {
......@@ -97,6 +95,7 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize
9795 end = new_end;
9896 continue;
9997 }
98 if (end == 0) return bw.unbuffered_writer.writev(data);
10099 var buffers: [max_buffers_len][]const u8 = undefined;
101100 buffers[0] = buffer[0..end];
102101 const remaining_data = data[i..];
......@@ -365,6 +364,7 @@ fn passthru_writeFile(
365364) anyerror!usize {
366365 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
367366 const buffer = bw.buffer;
367 if (buffer.len == 0) return bw.unbuffered_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
368368 const start_end = bw.end;
369369 const headers = headers_and_trailers[0..headers_len];
370370 const trailers = headers_and_trailers[headers_len..];