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 {...@@ -1591,8 +1591,7 @@ pub fn writer(file: File) std.io.Writer {
1591 return .{1591 return .{
1592 .context = interface.handleToOpaque(file.handle),1592 .context = interface.handleToOpaque(file.handle),
1593 .vtable = &.{1593 .vtable = &.{
1594 .writev = interface.writev,1594 .writeSplat = interface.writeSplat,
1595 .splat = interface.splat,
1596 .writeFile = interface.writeFile,1595 .writeFile = interface.writeFile,
1597 },1596 },
1598 };1597 };
...@@ -1610,45 +1609,20 @@ const interface = struct {...@@ -1610,45 +1609,20 @@ const interface = struct {
1610 /// vectors through the underlying write calls as possible.1609 /// vectors through the underlying write calls as possible.
1611 const max_buffers_len = 16;1610 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 {
1614 const file = opaqueToHandle(context);1613 const file = opaqueToHandle(context);
1615
1616 if (is_windows) {1614 if (is_windows) {
1617 // TODO improve this to use WriteFileScatter1615 if (data.len == 1 and splat == 0) return 0;
1618 if (data.len == 0) return 0;1616 return windows.WriteFile(file, data[0], null);
1619 const first = data[0];
1620 return windows.WriteFile(file, first.base[0..first.len], null);
1621 }1617 }
1622
1623 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;1618 var iovecs_buffer: [max_buffers_len]std.posix.iovec_const = undefined;
1624 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)];1619 const iovecs = iovecs_buffer[0..@min(iovecs_buffer.len, data.len)];
1625 for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{1620 for (iovecs, data[0..iovecs.len]) |*v, d| v.* = .{
1626 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.1621 .base = if (d.len == 0) "" else d.ptr, // OS sadly checks ptr addr before length.
1627 .len = d.len,1622 .len = d.len,
1628 };1623 };
1629 return std.posix.writev(file, iovecs);1624 const send_iovecs = if (splat == 0) iovecs[0 .. iovecs.len - 1] else iovecs;
1630 }1625 return std.posix.writev(file, send_iovecs);
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);
1652 }1626 }
16531627
1654 fn writeFile(1628 fn writeFile(
...@@ -1662,7 +1636,7 @@ const interface = struct {...@@ -1662,7 +1636,7 @@ const interface = struct {
1662 const out_fd = opaqueToHandle(context);1636 const out_fd = opaqueToHandle(context);
1663 const in_fd = in_file.handle;1637 const in_fd = in_file.handle;
1664 const len_int = switch (in_len) {1638 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),
1666 .entire_file => 0,1640 .entire_file => 0,
1667 else => in_len.int(),1641 else => in_len.int(),
1668 };1642 };
lib/std/io.zig+5-11
...@@ -344,22 +344,16 @@ pub const tty = @import("io/tty.zig");...@@ -344,22 +344,16 @@ pub const tty = @import("io/tty.zig");
344pub const null_writer: Writer = .{344pub const null_writer: Writer = .{
345 .context = undefined,345 .context = undefined,
346 .vtable = &.{346 .vtable = &.{
347 .writev = null_writev,347 .writeSplat = null_writeSplat,
348 .splat = null_splat,
349 .writeFile = null_writeFile,348 .writeFile = null_writeFile,
350 },349 },
351};350};
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 {
354 _ = context;353 _ = context;
355 var written: usize = 0;354 const headers = data[0 .. data.len - 1];
356 for (data) |bytes| written += bytes.len;355 const pattern = data[headers.len..];
357 return written;356 var written: usize = pattern.len * splat;
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;
363 for (headers) |bytes| written += bytes.len;357 for (headers) |bytes| written += bytes.len;
364 return written;358 return written;
365}359}
lib/std/io/AllocatingWriter.zig+17-15
...@@ -21,7 +21,7 @@ allocator: std.mem.Allocator,...@@ -21,7 +21,7 @@ allocator: std.mem.Allocator,
21buffered_writer: std.io.BufferedWriter,21buffered_writer: std.io.BufferedWriter,
2222
23const vtable: std.io.Writer.VTable = .{23const vtable: std.io.Writer.VTable = .{
24 .writev = writev,24 .writeSplat = writeSplat,
25 .writeFile = writeFile,25 .writeFile = writeFile,
26};26};
2727
...@@ -98,35 +98,37 @@ pub fn clearRetainingCapacity(aw: *AllocatingWriter) void {...@@ -98,35 +98,37 @@ pub fn clearRetainingCapacity(aw: *AllocatingWriter) void {
98 aw.written.len = 0;98 aw.written.len = 0;
99}99}
100100
101fn writev(context: *anyopaque, data: []const []const u8) anyerror!usize {101fn writeSplat(context: *anyopaque, data: []const []const u8, splat: usize) 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 {
106 const aw: *AllocatingWriter = @alignCast(@ptrCast(context));102 const aw: *AllocatingWriter = @alignCast(@ptrCast(context));
107 const start_len = aw.written.len;103 const start_len = aw.written.len;
108 const bw = &aw.buffered_writer;104 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;
110 var list: std.ArrayListUnmanaged(u8) = .{107 var list: std.ArrayListUnmanaged(u8) = .{
111 .items = aw.written.ptr[0 .. start_len + headers[0].len],108 .items = aw.written.ptr[0..items_len],
112 .capacity = start_len + bw.buffer.len,109 .capacity = start_len + bw.buffer.len,
113 };110 };
114 defer setArrayList(aw, list);111 defer setArrayList(aw, list);
115 const rest = headers[1..];112 const rest = data[1 .. data.len - 1];
116 var new_capacity: usize = list.capacity + pattern.len * n;113 const pattern = data[data.len - 1];
114 var new_capacity: usize = list.capacity + pattern.len * splat;
117 for (rest) |bytes| new_capacity += bytes.len;115 for (rest) |bytes| new_capacity += bytes.len;
118 try list.ensureTotalCapacity(aw.allocator, new_capacity + 1);116 try list.ensureTotalCapacity(aw.allocator, new_capacity + 1);
119 for (rest) |bytes| list.appendSliceAssumeCapacity(bytes);117 for (rest) |bytes| list.appendSliceAssumeCapacity(bytes);
120 if (pattern.len == 1) {118 appendPatternAssumeCapacity(&list, pattern, splat);
121 list.appendNTimesAssumeCapacity(pattern[0], n);
122 } else {
123 for (0..n) |_| list.appendSliceAssumeCapacity(pattern);
124 }
125 aw.written = list.items;119 aw.written = list.items;
126 bw.buffer = list.unusedCapacitySlice();120 bw.buffer = list.unusedCapacitySlice();
127 return list.items.len - start_len;121 return list.items.len - start_len;
128}122}
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
130fn writeFile(132fn writeFile(
131 context: *anyopaque,133 context: *anyopaque,
132 file: std.fs.File,134 file: std.fs.File,
lib/std/io/BufferedWriter.zig+69-76
...@@ -33,16 +33,14 @@ pub fn writer(bw: *BufferedWriter) Writer {...@@ -33,16 +33,14 @@ pub fn writer(bw: *BufferedWriter) Writer {
33 return .{33 return .{
34 .context = bw,34 .context = bw,
35 .vtable = &.{35 .vtable = &.{
36 .writev = passthru_writev,36 .write = passthru_writeSplat,
37 .splat = passthru_splat,
38 .writeFile = passthru_writeFile,37 .writeFile = passthru_writeFile,
39 },38 },
40 };39 };
41}40}
4241
43const fixed_vtable: Writer.VTable = .{42const fixed_vtable: Writer.VTable = .{
44 .writev = fixed_writev,43 .writeSplat = fixed_writeSplat,
45 .splat = fixed_splat,
46 .writeFile = fixed_writeFile,44 .writeFile = fixed_writeFile,
47};45};
4846
...@@ -81,7 +79,7 @@ pub fn flush(bw: *BufferedWriter) anyerror!void {...@@ -81,7 +79,7 @@ pub fn flush(bw: *BufferedWriter) anyerror!void {
81pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void {79pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void {
82 var i: usize = 0;80 var i: usize = 0;
83 while (true) {81 while (true) {
84 var n = try writev(bw, data[i..]);82 var n = try passthru_writeSplat(bw, data[i..], 1);
85 while (n >= data[i].len) {83 while (n >= data[i].len) {
86 n -= data[i].len;84 n -= data[i].len;
87 i += 1;85 i += 1;
...@@ -91,14 +89,16 @@ pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void {...@@ -91,14 +89,16 @@ pub fn writevAll(bw: *BufferedWriter, data: []const []const u8) anyerror!void {
91 }89 }
92}90}
9391
94pub fn writev(bw: *BufferedWriter, data: []const []const u8) anyerror!usize {92pub fn writeSplat(bw: *BufferedWriter, data: []const []const u8, splat: usize) anyerror!usize {
95 return passthru_writev(bw, data);93 return passthru_writeSplat(bw, data, splat);
96}94}
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 {
99 const bw: *BufferedWriter = @alignCast(@ptrCast(context));97 const bw: *BufferedWriter = @alignCast(@ptrCast(context));
100 const buffer = bw.buffer;98 const buffer = bw.buffer;
101 const start_end = bw.end;99 const start_end = bw.end;
100
101 var buffers: [max_buffers_len][]const u8 = undefined;
102 var end = bw.end;102 var end = bw.end;
103 for (data, 0..) |bytes, i| {103 for (data, 0..) |bytes, i| {
104 const new_end = end + bytes.len;104 const new_end = end + bytes.len;
...@@ -108,102 +108,79 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize...@@ -108,102 +108,79 @@ fn passthru_writev(context: *anyopaque, data: []const []const u8) anyerror!usize
108 end = new_end;108 end = new_end;
109 continue;109 continue;
110 }110 }
111 if (end == 0) return bw.unbuffered_writer.writev(data);111 if (end == 0) return bw.unbuffered_writer.writeSplat(data, splat);
112 var buffers: [max_buffers_len][]const u8 = undefined;
113 buffers[0] = buffer[0..end];112 buffers[0] = buffer[0..end];
114 const remaining_data = data[i..];113 const remaining_data = data[i..];
115 const remaining_buffers = buffers[1..];114 const remaining_buffers = buffers[1..];
116 const len: usize = @min(remaining_data.len, remaining_buffers.len);115 const len: usize = @min(remaining_data.len, remaining_buffers.len);
117 @memcpy(remaining_buffers[0..len], remaining_data[0..len]);116 @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]);
154 const send_buffers = buffers[0 .. len + 1];117 const send_buffers = buffers[0 .. len + 1];
155 if (len >= remaining_headers.len) {118 if (len >= remaining_data.len) {
156 @branchHint(.likely);119 @branchHint(.likely);
157 // Made it past the headers, so we can call `splat`.120 // Made it past the headers, so we can enable splatting.
158 const written = try bw.unbuffered_writer.splat(send_buffers, pattern, n);121 const n = try bw.unbuffered_writer.writeSplat(send_buffers, splat);
159 if (written < end) {122 if (n < end) {
160 @branchHint(.unlikely);123 @branchHint(.unlikely);
161 const remainder = buffer[written..end];124 const remainder = buffer[n..end];
162 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);125 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
163 bw.end = remainder.len;126 bw.end = remainder.len;
164 return end - start_end;127 return end - start_end;
165 }128 }
166 bw.end = 0;129 bw.end = 0;
167 return written - start_end;130 return n - start_end;
168 }131 }
169 const written = try bw.unbuffered_writer.writev(send_buffers);132 const n = try bw.unbuffered_writer.writeSplat(send_buffers, 1);
170 if (written < end) {133 if (n < end) {
171 @branchHint(.unlikely);134 @branchHint(.unlikely);
172 const remainder = buffer[written..end];135 const remainder = buffer[n..end];
173 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);136 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
174 bw.end = remainder.len;137 bw.end = remainder.len;
175 return end - start_end;138 return end - start_end;
176 }139 }
177 bw.end = 0;140 bw.end = 0;
178 return written - start_end;141 return n - start_end;
179 }142 }
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
181 switch (pattern.len) {156 switch (pattern.len) {
182 0 => {157 0 => {
183 bw.end = end;158 bw.end = end;
184 return end - start_end;159 return end - start_end;
185 },160 },
186 1 => {161 1 => {
187 const new_end = end + n;162 const new_end = end + remaining_splat;
188 if (new_end <= buffer.len) {163 if (new_end <= buffer.len) {
189 @branchHint(.likely);164 @branchHint(.likely);
190 @memset(buffer[end..new_end], pattern[0]);165 @memset(buffer[end..new_end], pattern[0]);
191 bw.end = new_end;166 bw.end = new_end;
192 return end - start_end;167 return end - start_end;
193 }168 }
194 const written = try bw.unbuffered_writer.splat(buffer[0..end], pattern, n);169 buffers[0] = buffer[0..end];
195 if (written < end) {170 buffers[1] = pattern;
171 const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
172 if (n < end) {
196 @branchHint(.unlikely);173 @branchHint(.unlikely);
197 const remainder = buffer[written..end];174 const remainder = buffer[n..end];
198 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);175 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
199 bw.end = remainder.len;176 bw.end = remainder.len;
200 return end - start_end;177 return end - start_end;
201 }178 }
202 bw.end = 0;179 bw.end = 0;
203 return written - start_end;180 return n - start_end;
204 },181 },
205 else => {182 else => {
206 const new_end = end + pattern.len * n;183 const new_end = end + pattern.len * remaining_splat;
207 if (new_end <= buffer.len) {184 if (new_end <= buffer.len) {
208 @branchHint(.likely);185 @branchHint(.likely);
209 while (end < new_end) : (end += pattern.len) {186 while (end < new_end) : (end += pattern.len) {
...@@ -212,16 +189,18 @@ fn passthru_splat(context: *anyopaque, headers: []const []const u8, pattern: []c...@@ -212,16 +189,18 @@ fn passthru_splat(context: *anyopaque, headers: []const []const u8, pattern: []c
212 bw.end = end;189 bw.end = end;
213 return end - start_end;190 return end - start_end;
214 }191 }
215 const written = try bw.unbuffered_writer.splat(buffer[0..end], pattern, n);192 buffers[0] = buffer[0..end];
216 if (written < end) {193 buffers[1] = pattern;
194 const n = try bw.unbuffered_writer.writeSplat(buffers[0..2], remaining_splat);
195 if (n < end) {
217 @branchHint(.unlikely);196 @branchHint(.unlikely);
218 const remainder = buffer[written..end];197 const remainder = buffer[n..end];
219 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);198 std.mem.copyForwards(u8, buffer[0..remainder.len], remainder);
220 bw.end = remainder.len;199 bw.end = remainder.len;
221 return end - start_end;200 return end - start_end;
222 }201 }
223 bw.end = 0;202 bw.end = 0;
224 return written - start_end;203 return n - start_end;
225 },204 },
226 }205 }
227}206}
...@@ -237,15 +216,24 @@ fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {...@@ -237,15 +216,24 @@ fn fixed_writev(context: *anyopaque, data: []const []const u8) anyerror!usize {
237 return error.NoSpaceLeft;216 return error.NoSpaceLeft;
238}217}
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 {
241 const bw: *BufferedWriter = @alignCast(@ptrCast(context));223 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];
242 const dest = bw.buffer[bw.end..];232 const dest = bw.buffer[bw.end..];
243 if (headers.len > 0) {233 switch (pattern.len) {
244 @memcpy(dest, headers[0][0..dest.len]);
245 } else switch (pattern.len) {
246 0 => unreachable,234 0 => unreachable,
247 1 => @memset(dest, pattern[0]),235 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),
249 }237 }
250 return error.NoSpaceLeft;238 return error.NoSpaceLeft;
251}239}
...@@ -329,21 +317,26 @@ pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void {...@@ -329,21 +317,26 @@ pub fn splatByteAll(bw: *BufferedWriter, byte: u8, n: usize) anyerror!void {
329///317///
330/// Does maximum of one underlying `Writer.VTable.writev`.318/// Does maximum of one underlying `Writer.VTable.writev`.
331pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize {319pub fn splatByte(bw: *BufferedWriter, byte: u8, n: usize) anyerror!usize {
332 return passthru_splat(bw, &.{}, &.{byte}, n);320 return passthru_writeSplat(bw, &.{&.{byte}}, n);
333}321}
334322
335/// Writes the same slice many times, performing the underlying write call as323/// Writes the same slice many times, performing the underlying write call as
336/// many times as necessary.324/// many times as necessary.
337pub fn splatBytesAll(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!void {325pub fn splatBytesAll(bw: *BufferedWriter, bytes: []const u8, splat: usize) anyerror!void {
338 var remaining: usize = n * bytes.len;326 var remaining_bytes: usize = bytes.len * splat;
339 while (remaining > 0) remaining -= try splatBytes(bw, bytes, remaining);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 }
340}333}
341334
342/// Writes the same slice many times, allowing short writes.335/// Writes the same slice many times, allowing short writes.
343///336///
344/// Does maximum of one underlying `Writer.VTable.writev`.337/// Does maximum of one underlying `Writer.VTable.writev`.
345pub fn splatBytes(bw: *BufferedWriter, bytes: []const u8, n: usize) anyerror!usize {338pub 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);
347}340}
348341
349/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.342/// 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 {...@@ -13,8 +13,7 @@ pub fn writer(cw: *CountingWriter) Writer {
13 return .{13 return .{
14 .context = cw,14 .context = cw,
15 .vtable = &.{15 .vtable = &.{
16 .writev = passthru_writev,16 .writeSplat = passthru_writeSplat,
17 .splat = passthru_splat,
18 .writeFile = passthru_writeFile,17 .writeFile = passthru_writeFile,
19 },18 },
20 };19 };
...@@ -27,18 +26,11 @@ pub fn unbufferedWriter(cw: *CountingWriter) std.io.BufferedWriter {...@@ -27,18 +26,11 @@ pub fn unbufferedWriter(cw: *CountingWriter) std.io.BufferedWriter {
27 };26 };
28}27}
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 {
31 const cw: *CountingWriter = @alignCast(@ptrCast(context));30 const cw: *CountingWriter = @alignCast(@ptrCast(context));
32 const written = try cw.child_writer.writev(data);31 const n = try cw.child_writer.writeSplat(data, splat);
33 cw.bytes_written += written;32 cw.bytes_written += n;
34 return written;33 return n;
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;
42}34}
4335
44fn passthru_writeFile(36fn passthru_writeFile(
...@@ -50,9 +42,9 @@ fn passthru_writeFile(...@@ -50,9 +42,9 @@ fn passthru_writeFile(
50 headers_len: usize,42 headers_len: usize,
51) anyerror!usize {43) anyerror!usize {
52 const cw: *CountingWriter = @alignCast(@ptrCast(context));44 const cw: *CountingWriter = @alignCast(@ptrCast(context));
53 const written = try cw.child_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);45 const n = try cw.child_writer.writeFile(file, offset, len, headers_and_trailers, headers_len);
54 cw.bytes_written += written;46 cw.bytes_written += n;
55 return written;47 return n;
56}48}
5749
58test CountingWriter {50test CountingWriter {
lib/std/io/Writer.zig+12-17
...@@ -8,25 +8,16 @@ vtable: *const VTable,...@@ -8,25 +8,16 @@ vtable: *const VTable,
8pub const VTable = struct {8pub const VTable = struct {
9 /// Each slice in `data` is written in order.9 /// Each slice in `data` is written in order.
10 ///10 ///
11 /// Number of bytes actually written is returned.11 /// `data.len` must be greater than zero, and the last element of `data` is
12 ///12 /// special. It is repeated as necessary so that it is written `splat`
13 /// Number of bytes returned may be zero, which does not mean13 /// number of times.
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`.
23 ///14 ///
24 /// Number of bytes actually written is returned.15 /// Number of bytes actually written is returned.
25 ///16 ///
26 /// Number of bytes returned may be zero, which does not mean17 /// Number of bytes returned may be zero, which does not mean
27 /// end-of-stream. A subsequent call may return nonzero, or may signal end18 /// end-of-stream. A subsequent call may return nonzero, or may signal end
28 /// of stream via an error.19 /// 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
31 /// Writes contents from an open file. `headers` are written first, then `len`22 /// Writes contents from an open file. `headers` are written first, then `len`
32 /// bytes of `file` starting from `offset`, then `trailers`.23 /// bytes of `file` starting from `offset`, then `trailers`.
...@@ -67,7 +58,11 @@ pub const VTable = struct {...@@ -67,7 +58,11 @@ pub const VTable = struct {
67};58};
6859
69pub fn writev(w: Writer, data: []const []const u8) anyerror!usize {60pub 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);
71}66}
7267
73pub fn writeFile(68pub fn writeFile(
...@@ -83,12 +78,12 @@ pub fn writeFile(...@@ -83,12 +78,12 @@ pub fn writeFile(
8378
84pub fn write(w: Writer, bytes: []const u8) anyerror!usize {79pub fn write(w: Writer, bytes: []const u8) anyerror!usize {
85 const single: [1][]const u8 = .{bytes};80 const single: [1][]const u8 = .{bytes};
86 return w.vtable.writev(w.context, &single);81 return w.vtable.writeSplat(w.context, &single, 1);
87}82}
8883
89pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void {84pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void {
90 var index: usize = 0;85 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);
92}87}
9388
94///// Directly calls `writeAll` many times to render the formatted text. To89///// 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 {...@@ -102,7 +97,7 @@ pub fn writeAll(w: Writer, bytes: []const u8) anyerror!void {
102pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void {97pub fn writevAll(w: Writer, data: [][]const u8) anyerror!void {
103 var i: usize = 0;98 var i: usize = 0;
104 while (true) {99 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);
106 while (n >= data[i].len) {101 while (n >= data[i].len) {
107 n -= data[i].len;102 n -= data[i].len;
108 i += 1;103 i += 1;