authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-13 22:02:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-14 12:56:37-07:00
logaf7e142485d422e1fdbff1dcfc6e95e4f9126453
treea59c0e59dfe5e56ba1195a08a5aefefa53847c73
parent96e4825fbba714322ad750ee179da3d5f8463bb5

std.Io.Writer: introduce rebase to the vtable

fixes #24814

2 files changed, 103 insertions(+), 61 deletions(-)

lib/std/Io/Writer.zig+102-58
...@@ -4,7 +4,7 @@ const native_endian = builtin.target.cpu.arch.endian();...@@ -4,7 +4,7 @@ const native_endian = builtin.target.cpu.arch.endian();
4const Writer = @This();4const Writer = @This();
5const std = @import("../std.zig");5const std = @import("../std.zig");
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const Limit = std.io.Limit;7const Limit = std.Io.Limit;
8const File = std.fs.File;8const File = std.fs.File;
9const testing = std.testing;9const testing = std.testing;
10const Allocator = std.mem.Allocator;10const Allocator = std.mem.Allocator;
...@@ -76,6 +76,14 @@ pub const VTable = struct {...@@ -76,6 +76,14 @@ pub const VTable = struct {
76 /// There may be subsequent calls to `drain` and `sendFile` after a `flush`76 /// There may be subsequent calls to `drain` and `sendFile` after a `flush`
77 /// operation.77 /// operation.
78 flush: *const fn (w: *Writer) Error!void = defaultFlush,78 flush: *const fn (w: *Writer) Error!void = defaultFlush,
79
80 /// Ensures `capacity` more bytes can be buffered without rebasing.
81 ///
82 /// The most recent `preserve` bytes must remain buffered.
83 ///
84 /// Only called when `capacity` bytes cannot fit into the unused capacity
85 /// of `buffer`.
86 rebase: *const fn (w: *Writer, preserve: usize, capacity: usize) Error!void = defaultRebase,
79};87};
8088
81pub const Error = error{89pub const Error = error{
...@@ -117,6 +125,7 @@ pub fn fixed(buffer: []u8) Writer {...@@ -117,6 +125,7 @@ pub fn fixed(buffer: []u8) Writer {
117 .vtable = &.{125 .vtable = &.{
118 .drain = fixedDrain,126 .drain = fixedDrain,
119 .flush = noopFlush,127 .flush = noopFlush,
128 .rebase = failingRebase,
120 },129 },
121 .buffer = buffer,130 .buffer = buffer,
122 };131 };
...@@ -130,6 +139,7 @@ pub const failing: Writer = .{...@@ -130,6 +139,7 @@ pub const failing: Writer = .{
130 .vtable = &.{139 .vtable = &.{
131 .drain = failingDrain,140 .drain = failingDrain,
132 .sendFile = failingSendFile,141 .sendFile = failingSendFile,
142 .rebase = failingRebase,
133 },143 },
134};144};
135145
...@@ -276,7 +286,7 @@ fn writeSplatHeaderLimitFinish(...@@ -276,7 +286,7 @@ fn writeSplatHeaderLimitFinish(
276286
277test "writeSplatHeader splatting avoids buffer aliasing temptation" {287test "writeSplatHeader splatting avoids buffer aliasing temptation" {
278 const initial_buf = try testing.allocator.alloc(u8, 8);288 const initial_buf = try testing.allocator.alloc(u8, 8);
279 var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf);289 var aw: Allocating = .initOwnedSlice(testing.allocator, initial_buf);
280 defer aw.deinit();290 defer aw.deinit();
281 // This test assumes 8 vector buffer in this function.291 // This test assumes 8 vector buffer in this function.
282 const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{292 const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{
...@@ -307,24 +317,41 @@ pub fn noopFlush(w: *Writer) Error!void {...@@ -307,24 +317,41 @@ pub fn noopFlush(w: *Writer) Error!void {
307317
308test "fixed buffer flush" {318test "fixed buffer flush" {
309 var buffer: [1]u8 = undefined;319 var buffer: [1]u8 = undefined;
310 var writer: std.io.Writer = .fixed(&buffer);320 var writer: Writer = .fixed(&buffer);
311321
312 try writer.writeByte(10);322 try writer.writeByte(10);
313 try writer.flush();323 try writer.flush();
314 try testing.expectEqual(10, buffer[0]);324 try testing.expectEqual(10, buffer[0]);
315}325}
316326
317/// Calls `VTable.drain` but hides the last `preserve_len` bytes from the327pub fn rebase(w: *Writer, preserve: usize, unused_capacity_len: usize) Error!void {
318/// implementation, keeping them buffered.328 if (w.buffer.len - w.end >= unused_capacity_len) {
319pub fn drainPreserve(w: *Writer, preserve_len: usize) Error!void {329 @branchHint(.likely);
320 const preserved_head = w.end -| preserve_len;330 return;
321 const preserved_tail = w.end;331 }
322 const preserved_len = preserved_tail - preserved_head;332 try w.vtable.rebase(w, preserve, unused_capacity_len);
323 w.end = preserved_head;333}
324 defer w.end += preserved_len;334
325 assert(0 == try w.vtable.drain(w, &.{""}, 1));335pub fn defaultRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
326 assert(w.end <= preserved_head + preserved_len);336 while (w.buffer.len - w.end < minimum_len) {
327 @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]);337 {
338 // TODO: instead of this logic that "hides" data from
339 // the implementation, introduce a seek index to Writer
340 const preserved_head = w.end -| preserve;
341 const preserved_tail = w.end;
342 const preserved_len = preserved_tail - preserved_head;
343 w.end = preserved_head;
344 defer w.end += preserved_len;
345 assert(0 == try w.vtable.drain(w, &.{""}, 1));
346 assert(w.end <= preserved_head + preserved_len);
347 @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]);
348 }
349
350 // If the loop condition was false this assertion would have passed
351 // anyway. Otherwise, give the implementation a chance to grow the
352 // buffer before asserting on the buffer length.
353 assert(w.buffer.len - preserve >= minimum_len);
354 }
328}355}
329356
330pub fn unusedCapacitySlice(w: *const Writer) []u8 {357pub fn unusedCapacitySlice(w: *const Writer) []u8 {
...@@ -353,53 +380,44 @@ pub fn writableSlice(w: *Writer, len: usize) Error![]u8 {...@@ -353,53 +380,44 @@ pub fn writableSlice(w: *Writer, len: usize) Error![]u8 {
353 return big_slice[0..len];380 return big_slice[0..len];
354}381}
355382
356/// Asserts the provided buffer has total capacity enough for `minimum_length`.383/// Asserts the provided buffer has total capacity enough for `minimum_len`.
357///384///
358/// Does not `advance` the buffer end position.385/// Does not `advance` the buffer end position.
359///386///
360/// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`.387/// If `minimum_len` is zero, this is equivalent to `unusedCapacitySlice`.
361pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 {388pub fn writableSliceGreedy(w: *Writer, minimum_len: usize) Error![]u8 {
362 while (w.buffer.len - w.end < minimum_length) {389 return writableSliceGreedyPreserve(w, 0, minimum_len);
363 assert(0 == try w.vtable.drain(w, &.{""}, 1));
364 // If the loop condition was false this assertion would have passed
365 // anyway. Otherwise, give the implementation a chance to grow the
366 // buffer before asserting on the buffer length.
367 assert(w.buffer.len >= minimum_length);
368 } else {
369 @branchHint(.likely);
370 return w.buffer[w.end..];
371 }
372}390}
373391
374/// Asserts the provided buffer has total capacity enough for `minimum_length`392/// Asserts the provided buffer has total capacity enough for `minimum_len`
375/// and `preserve_len` combined.393/// and `preserve` combined.
376///394///
377/// Does not `advance` the buffer end position.395/// Does not `advance` the buffer end position.
378///396///
379/// When draining the buffer, ensures that at least `preserve_len` bytes397/// When draining the buffer, ensures that at least `preserve` bytes
380/// remain buffered.398/// remain buffered.
381///399///
382/// If `preserve_len` is zero, this is equivalent to `writableSliceGreedy`.400/// If `preserve` is zero, this is equivalent to `writableSliceGreedy`.
383pub fn writableSliceGreedyPreserve(w: *Writer, preserve_len: usize, minimum_length: usize) Error![]u8 {401pub fn writableSliceGreedyPreserve(w: *Writer, preserve: usize, minimum_len: usize) Error![]u8 {
384 assert(w.buffer.len >= preserve_len + minimum_length);402 if (w.buffer.len - w.end >= minimum_len) {
385 while (w.buffer.len - w.end < minimum_length) {
386 try drainPreserve(w, preserve_len);
387 } else {
388 @branchHint(.likely);403 @branchHint(.likely);
389 return w.buffer[w.end..];404 return w.buffer[w.end..];
390 }405 }
406 try rebase(w, preserve, minimum_len);
407 assert(w.buffer.len >= preserve + minimum_len);
408 return w.buffer[w.end..];
391}409}
392410
393/// Asserts the provided buffer has total capacity enough for `len`.411/// Asserts the provided buffer has total capacity enough for `len`.
394///412///
395/// Advances the buffer end position by `len`.413/// Advances the buffer end position by `len`.
396///414///
397/// When draining the buffer, ensures that at least `preserve_len` bytes415/// When draining the buffer, ensures that at least `preserve` bytes
398/// remain buffered.416/// remain buffered.
399///417///
400/// If `preserve_len` is zero, this is equivalent to `writableSlice`.418/// If `preserve` is zero, this is equivalent to `writableSlice`.
401pub fn writableSlicePreserve(w: *Writer, preserve_len: usize, len: usize) Error![]u8 {419pub fn writableSlicePreserve(w: *Writer, preserve: usize, len: usize) Error![]u8 {
402 const big_slice = try w.writableSliceGreedyPreserve(preserve_len, len);420 const big_slice = try w.writableSliceGreedyPreserve(preserve, len);
403 advance(w, len);421 advance(w, len);
404 return big_slice[0..len];422 return big_slice[0..len];
405}423}
...@@ -708,16 +726,18 @@ pub fn writeByte(w: *Writer, byte: u8) Error!void {...@@ -708,16 +726,18 @@ pub fn writeByte(w: *Writer, byte: u8) Error!void {
708 }726 }
709}727}
710728
711/// When draining the buffer, ensures that at least `preserve_len` bytes729/// When draining the buffer, ensures that at least `preserve` bytes
712/// remain buffered.730/// remain buffered.
713pub fn writeBytePreserve(w: *Writer, preserve_len: usize, byte: u8) Error!void {731pub fn writeBytePreserve(w: *Writer, preserve: usize, byte: u8) Error!void {
714 while (w.buffer.len - w.end == 0) {732 if (w.buffer.len - w.end != 0) {
715 try drainPreserve(w, preserve_len);
716 } else {
717 @branchHint(.likely);733 @branchHint(.likely);
718 w.buffer[w.end] = byte;734 w.buffer[w.end] = byte;
719 w.end += 1;735 w.end += 1;
736 return;
720 }737 }
738 try w.vtable.rebase(w, preserve, 1);
739 w.buffer[w.end] = byte;
740 w.end += 1;
721}741}
722742
723/// Writes the same byte many times, performing the underlying write call as743/// Writes the same byte many times, performing the underlying write call as
...@@ -735,18 +755,18 @@ test splatByteAll {...@@ -735,18 +755,18 @@ test splatByteAll {
735 try testing.expectEqualStrings("7" ** 45, aw.writer.buffered());755 try testing.expectEqualStrings("7" ** 45, aw.writer.buffered());
736}756}
737757
738pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Error!void {758pub fn splatBytePreserve(w: *Writer, preserve: usize, byte: u8, n: usize) Error!void {
739 const new_end = w.end + n;759 const new_end = w.end + n;
740 if (new_end <= w.buffer.len) {760 if (new_end <= w.buffer.len) {
741 @memset(w.buffer[w.end..][0..n], byte);761 @memset(w.buffer[w.end..][0..n], byte);
742 w.end = new_end;762 w.end = new_end;
743 return;763 return;
744 }764 }
745 // If `n` is large, we can ignore `preserve_len` up to a point.765 // If `n` is large, we can ignore `preserve` up to a point.
746 var remaining = n;766 var remaining = n;
747 while (remaining > preserve_len) {767 while (remaining > preserve) {
748 assert(remaining != 0);768 assert(remaining != 0);
749 remaining -= try splatByte(w, byte, remaining - preserve_len);769 remaining -= try splatByte(w, byte, remaining - preserve);
750 if (w.end + remaining <= w.buffer.len) {770 if (w.end + remaining <= w.buffer.len) {
751 @memset(w.buffer[w.end..][0..remaining], byte);771 @memset(w.buffer[w.end..][0..remaining], byte);
752 w.end += remaining;772 w.end += remaining;
...@@ -754,9 +774,9 @@ pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Er...@@ -754,9 +774,9 @@ pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Er
754 }774 }
755 }775 }
756 // All the next bytes received must be preserved.776 // All the next bytes received must be preserved.
757 if (preserve_len < w.end) {777 if (preserve < w.end) {
758 @memmove(w.buffer[0..preserve_len], w.buffer[w.end - preserve_len ..][0..preserve_len]);778 @memmove(w.buffer[0..preserve], w.buffer[w.end - preserve ..][0..preserve]);
759 w.end = preserve_len;779 w.end = preserve;
760 }780 }
761 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);781 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
762}782}
...@@ -1667,7 +1687,7 @@ pub const ByteSizeUnits = enum {...@@ -1667,7 +1687,7 @@ pub const ByteSizeUnits = enum {
16671687
1668/// Format option `precision` is ignored when `value` is less than 1kB1688/// Format option `precision` is ignored when `value` is less than 1kB
1669pub fn printByteSize(1689pub fn printByteSize(
1670 w: *std.io.Writer,1690 w: *Writer,
1671 value: u64,1691 value: u64,
1672 comptime units: ByteSizeUnits,1692 comptime units: ByteSizeUnits,
1673 options: std.fmt.Options,1693 options: std.fmt.Options,
...@@ -2169,7 +2189,7 @@ test "fixed output" {...@@ -2169,7 +2189,7 @@ test "fixed output" {
21692189
2170test "writeSplat 0 len splat larger than capacity" {2190test "writeSplat 0 len splat larger than capacity" {
2171 var buf: [8]u8 = undefined;2191 var buf: [8]u8 = undefined;
2172 var w: std.io.Writer = .fixed(&buf);2192 var w: Writer = .fixed(&buf);
2173 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);2193 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2174 try testing.expectEqual(0, n);2194 try testing.expectEqual(0, n);
2175}2195}
...@@ -2188,6 +2208,13 @@ pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) File...@@ -2188,6 +2208,13 @@ pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) File
2188 return error.WriteFailed;2208 return error.WriteFailed;
2189}2209}
21902210
2211pub fn failingRebase(w: *Writer, preserve: usize, capacity: usize) Error!void {
2212 _ = w;
2213 _ = preserve;
2214 _ = capacity;
2215 return error.WriteFailed;
2216}
2217
2191pub const Discarding = struct {2218pub const Discarding = struct {
2192 count: u64,2219 count: u64,
2193 writer: Writer,2220 writer: Writer,
...@@ -2455,7 +2482,7 @@ pub fn Hashing(comptime Hasher: type) type {...@@ -2455,7 +2482,7 @@ pub fn Hashing(comptime Hasher: type) type {
2455/// Maintains `Writer` state such that it writes to the unused capacity of an2482/// Maintains `Writer` state such that it writes to the unused capacity of an
2456/// array list, filling it up completely before making a call through the2483/// array list, filling it up completely before making a call through the
2457/// vtable, causing a resize. Consequently, the same, optimized, non-generic2484/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2458/// machine code that uses `std.io.Reader`, such as formatted printing, takes2485/// machine code that uses `std.Io.Reader`, such as formatted printing, takes
2459/// the hot paths when using this API.2486/// the hot paths when using this API.
2460///2487///
2461/// When using this API, it is not necessary to call `flush`.2488/// When using this API, it is not necessary to call `flush`.
...@@ -2514,6 +2541,7 @@ pub const Allocating = struct {...@@ -2514,6 +2541,7 @@ pub const Allocating = struct {
2514 .drain = Allocating.drain,2541 .drain = Allocating.drain,
2515 .sendFile = Allocating.sendFile,2542 .sendFile = Allocating.sendFile,
2516 .flush = noopFlush,2543 .flush = noopFlush,
2544 .rebase = growingRebase,
2517 };2545 };
25182546
2519 pub fn deinit(a: *Allocating) void {2547 pub fn deinit(a: *Allocating) void {
...@@ -2595,7 +2623,7 @@ pub const Allocating = struct {...@@ -2595,7 +2623,7 @@ pub const Allocating = struct {
2595 return list.items.len - start_len;2623 return list.items.len - start_len;
2596 }2624 }
25972625
2598 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize {2626 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2599 if (File.Handle == void) return error.Unimplemented;2627 if (File.Handle == void) return error.Unimplemented;
2600 if (limit == .nothing) return 0;2628 if (limit == .nothing) return 0;
2601 const a: *Allocating = @fieldParentPtr("writer", w);2629 const a: *Allocating = @fieldParentPtr("writer", w);
...@@ -2612,6 +2640,15 @@ pub const Allocating = struct {...@@ -2612,6 +2640,15 @@ pub const Allocating = struct {
2612 return n;2640 return n;
2613 }2641 }
26142642
2643 fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
2644 _ = preserve; // This implementation always preserves the entire buffer.
2645 const a: *Allocating = @fieldParentPtr("writer", w);
2646 const gpa = a.allocator;
2647 var list = a.toArrayList();
2648 defer setArrayList(a, list);
2649 list.ensureUnusedCapacity(gpa, minimum_len) catch return error.WriteFailed;
2650 }
2651
2615 fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void {2652 fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void {
2616 a.writer.buffer = list.allocatedSlice();2653 a.writer.buffer = list.allocatedSlice();
2617 a.writer.end = list.items.len;2654 a.writer.end = list.items.len;
...@@ -2645,7 +2682,7 @@ test "discarding sendFile" {...@@ -2645,7 +2682,7 @@ test "discarding sendFile" {
2645 try file_reader.seekTo(0);2682 try file_reader.seekTo(0);
26462683
2647 var w_buffer: [256]u8 = undefined;2684 var w_buffer: [256]u8 = undefined;
2648 var discarding: std.io.Writer.Discarding = .init(&w_buffer);2685 var discarding: Writer.Discarding = .init(&w_buffer);
26492686
2650 _ = try file_reader.interface.streamRemaining(&discarding.writer);2687 _ = try file_reader.interface.streamRemaining(&discarding.writer);
2651}2688}
...@@ -2664,7 +2701,7 @@ test "allocating sendFile" {...@@ -2664,7 +2701,7 @@ test "allocating sendFile" {
2664 var file_reader = file_writer.moveToReader();2701 var file_reader = file_writer.moveToReader();
2665 try file_reader.seekTo(0);2702 try file_reader.seekTo(0);
26662703
2667 var allocating: std.io.Writer.Allocating = .init(testing.allocator);2704 var allocating: Writer.Allocating = .init(testing.allocator);
2668 defer allocating.deinit();2705 defer allocating.deinit();
26692706
2670 _ = try file_reader.interface.streamRemaining(&allocating.writer);2707 _ = try file_reader.interface.streamRemaining(&allocating.writer);
...@@ -2702,3 +2739,10 @@ test writeSliceEndian {...@@ -2702,3 +2739,10 @@ test writeSliceEndian {
2702 try writeSliceEndian(&w, u16, &array, .big);2739 try writeSliceEndian(&w, u16, &array, .big);
2703 try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer);2740 try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer);
2704}2741}
2742
2743test "writableSlice with fixed writer" {
2744 var buf: [2]u8 = undefined;
2745 var w: std.Io.Writer = .fixed(&buf);
2746 try w.writeByte(1);
2747 try std.testing.expectError(error.WriteFailed, w.writableSlice(2));
2748}
lib/std/compress/flate/Decompress.zig+1-3
...@@ -76,7 +76,7 @@ const indirect_vtable: Reader.VTable = .{...@@ -76,7 +76,7 @@ const indirect_vtable: Reader.VTable = .{
76/// `input` buffer is asserted to be at least 10 bytes, or EOF before then.76/// `input` buffer is asserted to be at least 10 bytes, or EOF before then.
77///77///
78/// If `buffer` is provided then asserted to have `flate.max_window_len`78/// If `buffer` is provided then asserted to have `flate.max_window_len`
79/// capacity, as well as `flate.history_len` unused capacity on every write.79/// capacity.
80pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {80pub fn init(input: *Reader, container: Container, buffer: []u8) Decompress {
81 if (buffer.len != 0) assert(buffer.len >= flate.max_window_len);81 if (buffer.len != 0) assert(buffer.len >= flate.max_window_len);
82 return .{82 return .{
...@@ -239,8 +239,6 @@ fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol {...@@ -239,8 +239,6 @@ fn decodeSymbol(self: *Decompress, decoder: anytype) !Symbol {
239}239}
240240
241fn streamDirect(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {241fn streamDirect(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
242 assert(w.buffer.len >= flate.max_window_len);
243 assert(w.unusedCapacityLen() >= flate.history_len);
244 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));242 const d: *Decompress = @alignCast(@fieldParentPtr("reader", r));
245 return streamFallible(d, w, limit);243 return streamFallible(d, w, limit);
246}244}