authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-02-15 21:02:42-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:25-07:00
log35824e4822acbb1922a5890c5753b48627e3982f
tree0430b2e39c717f942adc769cb5855ccd204369f1
parentb26aceba7d5a4e0facb6589b3a6d659ae301d70b

writeSplat


6 files changed, 118 insertions(+), 168 deletions(-)

lib/std/fs/File.zig+7-33
......@@ -1591,8 +1591,7 @@ pub fn writer(file: File) std.io.Writer {
15911591 return .{
15921592 .context = interface.handleToOpaque(file.handle),
15931593 .vtable = &.{
1594 .writev = interface.writev,
1595 .splat = interface.splat,
1594 .writeSplat = interface.writeSplat,
15961595 .writeFile = interface.writeFile,
15971596 },
15981597 };
......@@ -1610,45 +1609,20 @@ const interface = struct {
16101609 /// vectors through the underlying write calls as possible.
16111610 const max_buffers_len = 16;
16121611
1613 fn writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
1612 fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
16141613 const file = opaqueToHandle(context);
1615
16161614 if (is_windows) {
1617 // TODO improve this to use WriteFileScatter
1618 if (data.len == 0) return 0;
1619 const first = data[0];
1620 return windows.WriteFile(file, first.base[0..first.len], null);
1615 if (data.len == 1 and splat == 0) return 0;
1616 return windows.WriteFile(file, data[0], null);
16211617 }
1622
16231618 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
16241619 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)];
16251620 for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{
16261621 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
16271622 .len = d.len,
16281623 };
1629 return std.posix.writev(file, iovecs);
1630 }
1631
1632 fn splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize {
1633 const file = opaqueToHandle(context);
1634
1635 if (is_windows) {
1636 // TODO improve this to use WriteFileScatter
1637 if (headers.len > 0) {
1638 const first = headers[0];
1639 return windows.WriteFile(file, first, null);
1640 }
1641 if (n > 0) return windows.WriteFile(file, pattern, null);
1642 return 0;
1643 }
1644
1645 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1646 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, headers.len)];
1647 for (iovecs, headers[0..iovecs.len]) |*v, d| v.* = .{
1648 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1649 .len = d.len,
1650 };
1651 return std.posix.writev(file, iovecs);
1624 const send_iovecs = if (splat == 0) iovecs[0 .. iovecs.len - 1] else iovecs;
1625 return std.posix.writev(file, send_iovecs);
16521626 }
16531627
16541628 fn writeFile(
......@@ -1662,7 +1636,7 @@ const interface = struct {
16621636 const out_fd = opaqueToHandle(context);
16631637 const in_fd = in_file.handle;
16641638 const len_int = switch (in_len) {
1665 .zero => return interface.writev(context, headers_and_trailers),
1639 .zero => return interface.writeSplat(context, headers_and_trailers, 1),
16661640 .entire_file => 0,
16671641 else => in_len.int(),
16681642 };
lib/std/io.zig+5-11
......@@ -344,22 +344,16 @@ pub const tty = @import("io/tty.zig");
344344pub const null_writer: Writer = .{
345345 .context = undefined,
346346 .vtable = &.{
347 .writev = null_writev,
348 .splat = null_splat,
347 .writeSplat = null_writeSplat,
349348 .writeFile = null_writeFile,
350349 },
351350};
352351
353fn null_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
352fn null_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
354353 _ = context;
355 var written: usize = 0;
356 for (data) |bytes| written += bytes.len;
357 return written;
358}
359
360fn null_splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize {
361 _ = context;
362 var written: usize = pattern.len * n;
354 const headers = data[0 .. data.len - 1];
355 const pattern = data[headers.len..];
356 var written: usize = pattern.len * splat;
363357 for (headers) |bytes| written += bytes.len;
364358 return written;
365359}
lib/std/io/AllocatingWriter.zig+17-15
......@@ -21,7 +21,7 @@ allocator: std.mem.Allocator,
2121buffered_writer: std.io.BufferedWriter,
2222
2323const vtable: std.io.Writer.VTable = .{
24 .writev = writev,
24 .writeSplat = writeSplat,
2525 .writeFile = writeFile,
2626};
2727
......@@ -98,35 +98,37 @@ pub fn clearRetainingCapacity(aw: *AllocatingWriter) void {
9898 aw.written.len = 0;
9999}
100100
101fn writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
102 return splat(context, data, &.{}, 0);
103}
104
105fn splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize {
101fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
106102 const aw: *AllocatingWriter = @alignCast(@ptrCast(context));
107103 const start_len = aw.written.len;
108104 const bw = &aw.buffered_writer;
109 assert(headers[0].ptr == aw.written.ptr + start_len);
105 const skip_first = data[0].ptr == aw.written.ptr + start_len;
106 const items_len = if (skip_first) start_len + data[0].len else start_len;
110107 var list: std.ArrayListUnmanaged(u8) = .{
111 .items = aw.written.ptr[0 .. start_len + headers[0].len],
108 .items = aw.written.ptr[0..items_len],
112109 .capacity = start_len + bw.buffer.len,
113110 };
114111 defer setArrayList(aw, list);
115 const rest = headers[1..];
116 var new_capacity: usize = list.capacity + pattern.len * n;
112 const rest = data[1 .. data.len - 1];
113 const pattern = data[data.len - 1];
114 var new_capacity: usize = list.capacity + pattern.len * splat;
117115 for (rest) |bytes| new_capacity += bytes.len;
118116 try list.ensureTotalCapacity(aw.allocator, new_capacity + 1);
119117 for (rest) |bytes| list.appendSliceAssumeCapacity(bytes);
120 if (pattern.len == 1) {
121 list.appendNTimesAssumeCapacity(pattern[0], n);
122 } else {
123 for (0..n) |_| list.appendSliceAssumeCapacity(pattern);
124 }
118 appendPatternAssumeCapacity(&list, pattern, splat);
125119 aw.written = list.items;
126120 bw.buffer = list.unusedCapacitySlice();
127121 return list.items.len - start_len;
128122}
129123
124fn appendPatternAssumeCapacity(list: *std.ArrayListUnmanaged(u8), pattern: []const u8, splat: usize) void {
125 if (pattern.len == 1) {
126 list.appendNTimesAssumeCapacity(pattern[0], splat);
127 } else {
128 for (0..splat) |_| list.appendSliceAssumeCapacity(pattern);
129 }
130}
131
130132fn writeFile(
131133 context: *anyopaque,
132134 file: std.fs.File,
lib/std/io/BufferedWriter.zig+69-76
......@@ -33,16 +33,14 @@ pub fn writer(bw: *BufferedWriter) Writer {
3333 return .{
3434 .context = bw,
3535 .vtable = &.{
36 .writev = passthru_writev,
37 .splat = passthru_splat,
36 .write = passthru_writeSplat,
3837 .writeFile = passthru_writeFile,
3938 },
4039 };
4140}
4241
4342const fixed_vtable: Writer.VTable = .{
44 .writev = fixed_writev,
45 .splat = fixed_splat,
43 .writeSplat = fixed_writeSplat,
4644 .writeFile = fixed_writeFile,
4745};
4846
......@@ -81,7 +79,7 @@ pub fn flush(bw: *BufferedWriter) anyerror!void {
8179pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void {
8280 var i: usize = 0;
8381 while (true) {
84 var n = try writev(bw, data[i..]);
82 var n = try passthru_writeSplat(bw, data[i..], 1);
8583 while (n >= data[i].len) {
8684 n -= data[i].len;
8785 i += 1;
......@@ -91,14 +89,16 @@ pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void {
9189 }
9290}
9391
94pub fn writev(bw: *BufferedWriter, data: []const []const u8) anyerror!usize {
95 return passthru_writev(bw, data);
92pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) anyerror!usize {
93 return passthru_writeSplat(bw, data, splat);
9694}
9795
98fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
96fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
9997 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
10098 const buffer = bw.buffer;
10199 const start_end = bw.end;
100
101 var buffers: [max_buffers_len][]const u8 = undefined;
102102 var end = bw.end;
103103 for (data, 0..) |bytes, i| {
104104 const new_end = end + bytes.len;
......@@ -108,102 +108,79 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize
108108 end = new_end;
109109 continue;
110110 }
111 if (end == 0) return bw.unbuffered_writer.writev(data);
112 var buffers: [max_buffers_len][]const u8 = undefined;
111 if (end == 0) return bw.unbuffered_writer.writeSplat(data, splat);
113112 buffers[0] = buffer[0..end];
114113 const remaining_data = data[i..];
115114 const remaining_buffers = buffers[1..];
116115 const len: usize = @min(remaining_data.len, remaining_buffers.len);
117116 @memcpy(remaining_buffers[0..len], remaining_data[0..len]);
118 const n = try bw.unbuffered_writer.writev(buffers[0 .. len + 1]);
119 if (n < end) {
120 @branchHint(.unlikely);
121 const remainder = buffer[n..end];
122 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
123 bw.end = remainder.len;
124 return end - start_end;
125 }
126 bw.end = 0;
127 return n - start_end;
128 }
129 bw.end = end;
130 return end - start_end;
131}
132
133fn passthru_splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize {
134 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
135 const buffer = bw.buffer;
136 const start_end = bw.end;
137
138 var end = bw.end;
139 for (headers, 0..) |bytes, i| {
140 const new_end = end + bytes.len;
141 if (new_end <= buffer.len) {
142 @branchHint(.likely);
143 @memcpy(buffer[end..new_end], bytes);
144 end = new_end;
145 continue;
146 }
147 if (end == 0) return bw.unbuffered_writer.splat(headers, pattern, n);
148 var buffers: [max_buffers_len][]const u8 = undefined;
149 buffers[0] = buffer[0..end];
150 const remaining_headers = headers[i..];
151 const remaining_buffers = buffers[1..];
152 const len: usize = @min(remaining_headers.len, remaining_buffers.len);
153 @memcpy(remaining_buffers[0..len], remaining_headers[0..len]);
154117 const send_buffers = buffers[0 .. len + 1];
155 if (len >= remaining_headers.len) {
118 if (len >= remaining_data.len) {
156119 @branchHint(.likely);
157 // Made it past the headers, so we can call `splat`.
158 const written = try bw.unbuffered_writer.splat(send_buffers, pattern, n);
159 if (written < end) {
120 // Made it past the headers, so we can enable splatting.
121 const n = try bw.unbuffered_writer.writeSplat(send_buffers, splat);
122 if (n < end) {
160123 @branchHint(.unlikely);
161 const remainder = buffer[written..end];
124 const remainder = buffer[n..end];
162125 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
163126 bw.end = remainder.len;
164127 return end - start_end;
165128 }
166129 bw.end = 0;
167 return written - start_end;
130 return n - start_end;
168131 }
169 const written = try bw.unbuffered_writer.writev(send_buffers);
170 if (written < end) {
132 const n = try bw.unbuffered_writer.writeSplat(send_buffers, 1);
133 if (n < end) {
171134 @branchHint(.unlikely);
172 const remainder = buffer[written..end];
135 const remainder = buffer[n..end];
173136 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
174137 bw.end = remainder.len;
175138 return end - start_end;
176139 }
177140 bw.end = 0;
178 return written - start_end;
141 return n - start_end;
179142 }
180143
144 const pattern = data[data.len - 1];
145
146 if (splat == 0) {
147 @branchHint(.unlikely);
148 // It was added in the loop above; undo it here.
149 end -= pattern.len;
150 bw.end = end;
151 return end - start_end;
152 }
153
154 const remaining_splat = splat - 1;
155
181156 switch (pattern.len) {
182157 0 => {
183158 bw.end = end;
184159 return end - start_end;
185160 },
186161 1 => {
187 const new_end = end + n;
162 const new_end = end + remaining_splat;
188163 if (new_end <= buffer.len) {
189164 @branchHint(.likely);
190165 @memset(buffer[end..new_end], pattern[0]);
191166 bw.end = new_end;
192167 return end - start_end;
193168 }
194 const written = try bw.unbuffered_writer.splat(buffer[0..end], pattern, n);
195 if (written < end) {
169 buffers[0] = buffer[0..end];
170 buffers[1] = pattern;
171 const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
172 if (n < end) {
196173 @branchHint(.unlikely);
197 const remainder = buffer[written..end];
174 const remainder = buffer[n..end];
198175 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
199176 bw.end = remainder.len;
200177 return end - start_end;
201178 }
202179 bw.end = 0;
203 return written - start_end;
180 return n - start_end;
204181 },
205182 else => {
206 const new_end = end + pattern.len * n;
183 const new_end = end + pattern.len * remaining_splat;
207184 if (new_end <= buffer.len) {
208185 @branchHint(.likely);
209186 while (end < new_end) : (end += pattern.len) {
......@@ -212,16 +189,18 @@ fn passthru_splat(context: *anyopaque, headers: []const []const u8, pattern: []c
212189 bw.end = end;
213190 return end - start_end;
214191 }
215 const written = try bw.unbuffered_writer.splat(buffer[0..end], pattern, n);
216 if (written < end) {
192 buffers[0] = buffer[0..end];
193 buffers[1] = pattern;
194 const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
195 if (n < end) {
217196 @branchHint(.unlikely);
218 const remainder = buffer[written..end];
197 const remainder = buffer[n..end];
219198 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
220199 bw.end = remainder.len;
221200 return end - start_end;
222201 }
223202 bw.end = 0;
224 return written - start_end;
203 return n - start_end;
225204 },
226205 }
227206}
......@@ -237,15 +216,24 @@ fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
237216 return error.NoSpaceLeft;
238217}
239218
240fn fixed_splat(context: *anyopaque, headers: []const []const u8, pattern: []const u8, n: usize) anyerror!usize {
219/// When this function is called it means the buffer got full, so it's time
220/// to return an error. However, we still need to make sure all of the
221/// available buffer has been filled.
222fn fixed_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
241223 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
224 for (data) |bytes| {
225 const dest = bw.buffer[bw.end..];
226 if (dest.len == 0) return error.NoSpaceLeft;
227 const len = @min(bytes.len, dest.len);
228 @memcpy(dest[0..len], bytes[0..len]);
229 bw.end += len;
230 }
231 const pattern = data[data.len - 1];
242232 const dest = bw.buffer[bw.end..];
243 if (headers.len > 0) {
244 @memcpy(dest, headers[0][0..dest.len]);
245 } else switch (pattern.len) {
233 switch (pattern.len) {
246234 0 => unreachable,
247235 1 => @memset(dest, pattern[0]),
248 else => for (0..n) |i| @memcpy(dest[i * pattern.len ..][0..pattern.len], pattern),
236 else => for (0..splat - 1) |i| @memcpy(dest[i * pattern.len ..][0..pattern.len], pattern),
249237 }
250238 return error.NoSpaceLeft;
251239}
......@@ -329,21 +317,26 @@ pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void {
329317///
330318/// Does maximum of one underlying `Writer.VTable.writev`.
331319pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize {
332 return passthru_splat(bw, &.{}, &.{byte}, n);
320 return passthru_writeSplat(bw, &.{&.{byte}}, n);
333321}
334322
335323/// Writes the same slice many times, performing the underlying write call as
336324/// many times as necessary.
337pub fn splatBytesAll(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!void {
338 var remaining: usize = n * bytes.len;
339 while (remaining > 0) remaining -= try splatBytes(bw, bytes, remaining);
325pub fn splatBytesAll(bw: *BufferedWriter, bytes: []const u8, splat: usize) anyerror!void {
326 var remaining_bytes: usize = bytes.len * splat;
327 remaining_bytes -= try splatBytes(bw, bytes, splat);
328 while (remaining_bytes > 0) {
329 const leftover = remaining_bytes % bytes.len;
330 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes };
331 remaining_bytes -= try splatBytes(bw, &buffers, splat);
332 }
340333}
341334
342335/// Writes the same slice many times, allowing short writes.
343336///
344337/// Does maximum of one underlying `Writer.VTable.writev`.
345338pub fn splatBytes(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!usize {
346 return passthru_splat(bw, &.{}, bytes, n);
339 return passthru_writeSplat(bw, &.{bytes}, n);
347340}
348341
349342/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
lib/std/io/CountingWriter.zig+8-16
......@@ -13,8 +13,7 @@ pub fn writer(cw: *CountingWriter) Writer {
1313 return .{
1414 .context = cw,
1515 .vtable = &.{
16 .writev = passthru_writev,
17 .splat = passthru_splat,
16 .writeSplat = passthru_writeSplat,
1817 .writeFile = passthru_writeFile,
1918 },
2019 };
......@@ -27,18 +26,11 @@ pub fn unbufferedWriter(cw: *CountingWriter) std.io.BufferedWriter {
2726 };
2827}
2928
30fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
29fn passthru_writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
3130 const cw: *CountingWriter = @alignCast(@ptrCast(context));
32 const written = try cw.child_writer.writev(data);
33 cw.bytes_written += written;
34 return written;
35}
36
37fn passthru_splat(context: *anyopaque, header: []const u8, pattern: []const u8, n: usize) anyerror!usize {
38 const cw: *CountingWriter = @alignCast(@ptrCast(context));
39 const written = try cw.child_writer.splat(header, pattern, n);
40 cw.bytes_written += written;
41 return written;
31 const n = try cw.child_writer.writeSplat(data, splat);
32 cw.bytes_written += n;
33 return n;
4234}
4335
4436fn passthru_writeFile(
......@@ -50,9 +42,9 @@ fn passthru_writeFile(
5042 headers_len: usize,
5143) anyerror!usize {
5244 const cw: *CountingWriter = @alignCast(@ptrCast(context));
53 const written = try cw.child_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
54 cw.bytes_written += written;
55 return written;
45 const n = try cw.child_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
46 cw.bytes_written += n;
47 return n;
5648}
5749
5850test CountingWriter {
lib/std/io/Writer.zig+12-17
......@@ -8,25 +8,16 @@ vtable: *const VTable,
88pub const VTable = struct {
99 /// Each slice in `data` is written in order.
1010 ///
11 /// Number of bytes actually written is returned.
12 ///
13 /// Number of bytes returned may be zero, which does not mean
14 /// end-of-stream. A subsequent call may return nonzero, or may signal end
15 /// of stream via an error.
16 writev: *const fn (context: *anyopaque, data: []const []const u8) anyerror!usize,
17
18 /// `headers_and_pattern` must have length of at least one. The last slice
19 /// is `pattern` which is the byte sequence to repeat `n` times. The rest
20 /// of the slices are headers to write before the pattern.
21 ///
22 /// When `n == 1`, this is equivalent to `writev`.
11 /// `data.len` must be greater than zero, and the last element of `data` is
12 /// special. It is repeated as necessary so that it is written `splat`
13 /// number of times.
2314 ///
2415 /// Number of bytes actually written is returned.
2516 ///
2617 /// Number of bytes returned may be zero, which does not mean
2718 /// end-of-stream. A subsequent call may return nonzero, or may signal end
2819 /// of stream via an error.
29 splat: *const fn (context: *anyopaque, headers_and_pattern: []const []const u8, n: usize) anyerror!usize,
20 writeSplat: *const fn (context: *anyopaque, data: []const []const u8, splat: usize) anyerror!usize,
3021
3122 /// Writes contents from an open file. `headers` are written first, then `len`
3223 /// bytes of `file` starting from `offset`, then `trailers`.
......@@ -67,7 +58,11 @@ pub const VTable = struct {
6758};
6859
6960pub fn writev(w: Writer, data: []const []const u8) anyerror!usize {
70 return w.vtable.writev(w.context, data);
61 return w.vtable.writeSplat(w.context, data, 1);
62}
63
64pub fn writeSplat(w: Writer, data: []const []const u8, splat: usize) anyerror!usize {
65 return w.vtable.writeSplat(w.context, data, splat);
7166}
7267
7368pub fn writeFile(
......@@ -83,12 +78,12 @@ pub fn writeFile(
8378
8479pub fn write(w: Writer, bytes: []const u8) anyerror!usize {
8580 const single: [1][]const u8 = .{bytes};
86 return w.vtable.writev(w.context, &single);
81 return w.vtable.writeSplat(w.context, &single, 1);
8782}
8883
8984pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void {
9085 var index: usize = 0;
91 while (index < bytes.len) index += try write(w, bytes[index..]);
86 while (index < bytes.len) index += try w.vtable.writeSplat(w.context, &.{bytes[index..]}, 1);
9287}
9388
9489///// Directly calls `writeAll` many times to render the formatted text. To
......@@ -102,7 +97,7 @@ pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void {
10297pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void {
10398 var i: usize = 0;
10499 while (true) {
105 var n = try w.vtable.writev(w.context, data[i..]);
100 var n = try w.vtable.writeSplat(w.context, data[i..], 1);
106101 while (n >= data[i].len) {
107102 n -= data[i].len;
108103 i += 1;