Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
334 changes: 182 additions & 152 deletions lib/std/Io/Writer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2528,189 +2528,219 @@ pub fn Hashing(comptime Hasher: type) type {
};
}

pub const Allocating = AllocatingAligned(.fromByteUnits(1));

/// Maintains `Writer` state such that it writes to the unused capacity of an
/// array list, filling it up completely before making a call through the
/// vtable, causing a resize. Consequently, the same, optimized, non-generic
/// machine code that uses `std.Io.Reader`, such as formatted printing, takes
/// the hot paths when using this API.
///
/// When using this API, it is not necessary to call `flush`.
pub const Allocating = struct {
allocator: Allocator,
writer: Writer,
pub fn AllocatingAligned(comptime alignment: std.mem.Alignment) type {
return struct {
allocator: Allocator,
writer: Writer,

pub fn init(allocator: Allocator) Allocating {
return .{
.allocator = allocator,
.writer = .{
.buffer = &.{},
.vtable = &vtable,
},
};
}
const Self = @This();

pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
return .{
.allocator = allocator,
.writer = .{
.buffer = try allocator.alloc(u8, capacity),
.vtable = &vtable,
},
};
}
pub fn init(allocator: Allocator) Self {
return .{
.allocator = allocator,
.writer = .{
.buffer = &.{},
.vtable = &vtable,
},
};
}

pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
return .{
.allocator = allocator,
.writer = .{
.buffer = slice,
.vtable = &vtable,
},
};
}
pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Self {
return .{
.allocator = allocator,
.writer = .{
.buffer = try allocator.alloc(u8, capacity),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.buffer = try allocator.alloc(u8, capacity),
.buffer = try allocator.alignedAlloc(u8, alignment, capacity),

.vtable = &vtable,
},
};
}

/// Replaces `array_list` with empty, taking ownership of the memory.
pub fn fromArrayList(allocator: Allocator, array_list: *ArrayList(u8)) Allocating {
defer array_list.* = .empty;
return .{
.allocator = allocator,
.writer = .{
.vtable = &vtable,
.buffer = array_list.allocatedSlice(),
.end = array_list.items.len,
},
pub fn initOwnedSlice(allocator: Allocator, slice: []align(alignment.toByteUnits()) u8) Self {
return .{
.allocator = allocator,
.writer = .{
.buffer = slice,
.vtable = &vtable,
},
};
}

/// Replaces `array_list` with empty, taking ownership of the memory.
pub fn fromArrayList(allocator: Allocator, array_list: *std.array_list.Aligned(u8, alignment)) Self {
defer array_list.* = .empty;
return .{
.allocator = allocator,
.writer = .{
.vtable = &vtable,
.buffer = array_list.allocatedSlice(),
.end = array_list.items.len,
},
};
}

const vtable: VTable = .{
.drain = Self.drain,
.sendFile = Self.sendFile,
.flush = noopFlush,
.rebase = growingRebase,
};
}

const vtable: VTable = .{
.drain = Allocating.drain,
.sendFile = Allocating.sendFile,
.flush = noopFlush,
.rebase = growingRebase,
};
pub fn deinit(a: *Self) void {
const aligned_buffer: []align(alignment.toByteUnits()) u8 =
@alignCast(a.writer.buffer);
a.allocator.free(aligned_buffer);
a.* = undefined;
}

pub fn deinit(a: *Allocating) void {
a.allocator.free(a.writer.buffer);
a.* = undefined;
}
/// Returns an array list that takes ownership of the allocated memory.
/// Resets the `Allocating` to an empty state.
pub fn toArrayList(a: *Self) std.array_list.Aligned(u8, alignment) {
const w = &a.writer;
const result: std.array_list.Aligned(u8, alignment) = .{
.items = @alignCast(w.buffer[0..w.end]),
.capacity = w.buffer.len,
};
w.buffer = &.{};
w.end = 0;
return result;
}

/// Returns an array list that takes ownership of the allocated memory.
/// Resets the `Allocating` to an empty state.
pub fn toArrayList(a: *Allocating) ArrayList(u8) {
const w = &a.writer;
const result: ArrayList(u8) = .{
.items = w.buffer[0..w.end],
.capacity = w.buffer.len,
};
w.buffer = &.{};
w.end = 0;
return result;
}
pub fn ensureUnusedCapacity(a: *Self, additional_count: usize) Allocator.Error!void {
var list = a.toArrayList();
defer a.setArrayList(list);
return list.ensureUnusedCapacity(a.allocator, additional_count);
}

pub fn ensureUnusedCapacity(a: *Allocating, additional_count: usize) Allocator.Error!void {
var list = a.toArrayList();
defer a.setArrayList(list);
return list.ensureUnusedCapacity(a.allocator, additional_count);
}
pub fn ensureTotalCapacity(a: *Self, new_capacity: usize) Allocator.Error!void {
var list = a.toArrayList();
defer a.setArrayList(list);
return list.ensureTotalCapacity(a.allocator, new_capacity);
}

pub fn ensureTotalCapacity(a: *Allocating, new_capacity: usize) Allocator.Error!void {
var list = a.toArrayList();
defer a.setArrayList(list);
return list.ensureTotalCapacity(a.allocator, new_capacity);
}
pub fn toOwnedSlice(a: *Self) error{OutOfMemory}![]align(alignment.toByteUnits()) u8 {
var list = a.toArrayList();
defer a.setArrayList(list);
return list.toOwnedSlice(a.allocator);
}

pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {
var list = a.toArrayList();
defer a.setArrayList(list);
return list.toOwnedSlice(a.allocator);
}
pub fn toOwnedSliceSentinel(a: *Self, comptime sentinel: u8) error{OutOfMemory}![:sentinel]align(alignment.toByteUnits()) u8 {
const gpa = a.allocator;
var list = @This().toArrayList(a);
defer a.setArrayList(list);
return @alignCast(try list.toOwnedSliceSentinel(gpa, sentinel));
}

pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 {
const gpa = a.allocator;
var list = @This().toArrayList(a);
defer a.setArrayList(list);
return list.toOwnedSliceSentinel(gpa, sentinel);
}
pub fn written(a: *Self) []align(alignment.toByteUnits()) u8 {
return @alignCast(a.writer.buffered());
}

pub fn written(a: *Allocating) []u8 {
return a.writer.buffered();
}
pub fn shrinkRetainingCapacity(a: *Self, new_len: usize) void {
a.writer.end = new_len;
}

pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
a.writer.end = new_len;
}
pub fn clearRetainingCapacity(a: *Self) void {
a.shrinkRetainingCapacity(0);
}

pub fn clearRetainingCapacity(a: *Allocating) void {
a.shrinkRetainingCapacity(0);
}
fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
const a: *Self = @fieldParentPtr("writer", w);
const gpa = a.allocator;
const pattern = data[data.len - 1];
const splat_len = pattern.len * splat;
var list = a.toArrayList();
defer setArrayList(a, list);
const start_len = list.items.len;
assert(data.len != 0);
for (data) |bytes| {
list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed;
list.appendSliceAssumeCapacity(bytes);
}
if (splat == 0) {
list.items.len -= pattern.len;
} else switch (pattern.len) {
0 => {},
1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1),
else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern),
}
return list.items.len - start_len;
}

fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
const a: *Allocating = @fieldParentPtr("writer", w);
const gpa = a.allocator;
const pattern = data[data.len - 1];
const splat_len = pattern.len * splat;
var list = a.toArrayList();
defer setArrayList(a, list);
const start_len = list.items.len;
assert(data.len != 0);
for (data) |bytes| {
list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed;
list.appendSliceAssumeCapacity(bytes);
fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
if (File.Handle == void) return error.Unimplemented;
if (limit == .nothing) return 0;
const a: *Self = @fieldParentPtr("writer", w);
const gpa = a.allocator;
var list = a.toArrayList();
defer setArrayList(a, list);
const pos = file_reader.logicalPos();
const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line;
if (additional == 0) return error.EndOfStream;
list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
const dest = limit.slice(list.unusedCapacitySlice());
const n = try file_reader.read(dest);
list.items.len += n;
return n;
}
if (splat == 0) {
list.items.len -= pattern.len;
} else switch (pattern.len) {
0 => {},
1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1),
else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern),

fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
const a: *Self = @fieldParentPtr("writer", w);
const gpa = a.allocator;
var list = a.toArrayList();
defer setArrayList(a, list);
const total = std.math.add(usize, preserve, minimum_len) catch return error.WriteFailed;
list.ensureTotalCapacity(gpa, total) catch return error.WriteFailed;
list.ensureUnusedCapacity(gpa, minimum_len) catch return error.WriteFailed;
}
return list.items.len - start_len;
}

fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
if (File.Handle == void) return error.Unimplemented;
if (limit == .nothing) return 0;
const a: *Allocating = @fieldParentPtr("writer", w);
const gpa = a.allocator;
var list = a.toArrayList();
defer setArrayList(a, list);
const pos = file_reader.logicalPos();
const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line;
if (additional == 0) return error.EndOfStream;
list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
const dest = limit.slice(list.unusedCapacitySlice());
const n = try file_reader.read(dest);
list.items.len += n;
return n;
}
fn setArrayList(a: *Self, list: std.array_list.Aligned(u8, alignment)) void {
a.writer.buffer = list.allocatedSlice();
a.writer.end = list.items.len;
}

fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
const a: *Allocating = @fieldParentPtr("writer", w);
const gpa = a.allocator;
var list = a.toArrayList();
defer setArrayList(a, list);
const total = std.math.add(usize, preserve, minimum_len) catch return error.WriteFailed;
list.ensureTotalCapacity(gpa, total) catch return error.WriteFailed;
list.ensureUnusedCapacity(gpa, minimum_len) catch return error.WriteFailed;
}
test Self {
const gpa = testing.allocator;
{
var a: Self = .init(gpa);
defer a.deinit();
const w = &a.writer;

fn setArrayList(a: *Allocating, list: ArrayList(u8)) void {
a.writer.buffer = list.allocatedSlice();
a.writer.end = list.items.len;
}
const x: i32 = 42;
const y: i32 = 1234;
try w.print("x: {}\ny: {}\n", .{ x, y });

test Allocating {
var a: Allocating = .init(testing.allocator);
defer a.deinit();
const w = &a.writer;
try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.written());
}

const x: i32 = 42;
const y: i32 = 1234;
try w.print("x: {}\ny: {}\n", .{ x, y });
{ // check that things compile and types are correct
var a: Self = .init(gpa);
defer a.deinit();
var l: std.array_list.Aligned(u8, alignment) = a.toArrayList();
_ = Self.fromArrayList(gpa, &l);
a.setArrayList(l);
const s: []align(alignment.toByteUnits()) u8 = try a.toOwnedSlice();
defer gpa.free(s);
const s2: [:0]align(alignment.toByteUnits()) u8 = try a.toOwnedSliceSentinel(0);
defer gpa.free(s2);
}
}
};
}

try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.written());
}
};
test AllocatingAligned {
_ = AllocatingAligned(.fromByteUnits(4));
_ = AllocatingAligned(.fromByteUnits(8));
_ = AllocatingAligned(.fromByteUnits(16));
_ = AllocatingAligned(.fromByteUnits(32));
}

test "discarding sendFile" {
var tmp_dir = testing.tmpDir(.{});
Expand Down