Skip to content

Commit

Permalink
support packed structs in std.io.Reader and std.io.Writer
Browse files Browse the repository at this point in the history
  • Loading branch information
kj4tmp committed Oct 5, 2024
1 parent cfd3bcf commit 04a383b
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 17 deletions.
45 changes: 36 additions & 9 deletions lib/std/io/Reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -324,20 +324,47 @@ pub fn isBytes(self: Self, slice: []const u8) anyerror!bool {
return matches;
}

/// Read a struct from the stream.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn readStruct(self: Self, comptime T: type) anyerror!T {
// Only extern and packed structs have defined in-memory layout.
comptime assert(@typeInfo(T).@"struct".layout != .auto);
var res: [1]T = undefined;
try self.readNoEof(mem.sliceAsBytes(res[0..]));
return res[0];
switch (@typeInfo(T).@"struct".layout) {
.auto => @compileError("readStruct only supports packed and extern structs."),
.@"extern" => {
var res: [1]T = undefined;
try self.readNoEof(mem.sliceAsBytes(res[0..]));
return res[0];
},
.@"packed" => {
var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined;
try self.readNoEof(&bytes);
return @bitCast(bytes);
},
}
}

/// Read a struct having the specified endianness into the host endianness representation.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
var res = try self.readStruct(T);
if (native_endian != endian) {
mem.byteSwapAllFields(T, &res);
switch (@typeInfo(T).@"struct".layout) {
.auto => @compileError("readStructEndian only supports packed and extern structs."),
.@"extern" => {
var res = try self.readStruct(T);
if (native_endian != endian) {
mem.byteSwapAllFields(T, &res);
}
return res;
},
.@"packed" => {
var bytes: [@divExact(@bitSizeOf(T), 8)]u8 = undefined;
try self.readNoEof(&bytes);
if (native_endian != endian) {
mem.reverse(u8, &bytes);
}
return @bitCast(bytes);
},
}
return res;
}

/// Reads an integer with the same size as the given enum's tag type. If the integer matches
Expand Down
42 changes: 42 additions & 0 deletions lib/std/io/Reader/test.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const builtin = @import("builtin");
const std = @import("../../std.zig");
const testing = std.testing;
const native_endian = @import("builtin").target.cpu.arch.endian();

test "Reader" {
var buf = "a\x02".*;
Expand Down Expand Up @@ -370,3 +371,44 @@ test "readIntoBoundedBytes correctly reads into a provided bounded array" {
try reader.readIntoBoundedBytes(10000, &bounded_array);
try testing.expectEqualStrings(bounded_array.slice(), test_string);
}

test "readStructEndian reads packed structs without padding and in correct field order" {
const buf = [3]u8{ 11, 12, 13 };
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();

const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };

try testing.expectEqualDeep(
PackedStruct{ .a = 11, .b = 12, .c = 13 },
reader.readStructEndian(PackedStruct, .little),
);
fis.reset();
try testing.expectEqualDeep(
PackedStruct{ .a = 13, .b = 12, .c = 11 },
reader.readStructEndian(PackedStruct, .big),
);
}

test "readStruct reads packed structs without padding and in correct field order" {
const buf = [3]u8{ 11, 12, 13 };
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();

const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };

switch (native_endian) {
.little => {
try testing.expectEqualDeep(
PackedStruct{ .a = 11, .b = 12, .c = 13 },
reader.readStruct(PackedStruct),
);
},
.big => {
try testing.expectEqualDeep(
PackedStruct{ .a = 13, .b = 12, .c = 11 },
reader.readStruct(PackedStruct),
);
},
}
}
46 changes: 38 additions & 8 deletions lib/std/io/Writer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,46 @@ pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.built
return self.writeAll(&bytes);
}

/// Write a struct to the stream.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn writeStruct(self: Self, value: anytype) anyerror!void {
// Only extern and packed structs have defined in-memory layout.
comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
return self.writeAll(mem.asBytes(&value));
switch (@typeInfo(@TypeOf(value)).@"struct".layout) {
.auto => @compileError("writeStruct only supports packed and extern structs."),
.@"extern" => {
return try self.writeAll(mem.asBytes(&value));
},
.@"packed" => {
const bytes: [@divExact(@bitSizeOf(@TypeOf(value)), 8)]u8 = @bitCast(value);
try self.writeAll(&bytes);
},
}
}

/// Write a struct to the stream in the specified endianness.
/// Only packed and extern structs are supported.
/// Packed structs must have a `@bitSizeOf` that is a multiple of eight.
pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void {
// TODO: make sure this value is not a reference type
if (native_endian == endian) {
return self.writeStruct(value);
} else {
var copy = value;
mem.byteSwapAllFields(@TypeOf(value), &copy);
return self.writeStruct(copy);
switch (@typeInfo(@TypeOf(value)).@"struct".layout) {
.auto => @compileError("writeStructEndian only supports packed and extern structs."),
.@"extern" => {
if (native_endian == endian) {
return try self.writeStruct(value);
} else {
var copy = value;
mem.byteSwapAllFields(@TypeOf(value), &copy);
return try self.writeStruct(copy);
}
},
.@"packed" => {
var bytes: [@divExact(@bitSizeOf(@TypeOf(value)), 8)]u8 = @bitCast(value);
if (native_endian != endian) {
mem.reverse(u8, &bytes);
}
return try self.writeAll(&bytes);
},
}
}

Expand All @@ -81,3 +107,7 @@ pub fn writeFile(self: Self, file: std.fs.File) anyerror!void {
if (n < buf.len) return;
}
}

test {
_ = @import("Writer/test.zig");
}
35 changes: 35 additions & 0 deletions lib/std/io/Writer/test.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const std = @import("../../std.zig");
const testing = std.testing;
const native_endian = @import("builtin").target.cpu.arch.endian();

test "writeStruct writes packed structs without padding" {
var buf: [3]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const writer = fis.writer();

const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };

try writer.writeStruct(PackedStruct{ .a = 11, .b = 12, .c = 13 });
switch (native_endian) {
.little => {
try testing.expectEqualSlices(u8, &.{ 11, 12, 13 }, &buf);
},
.big => {
try testing.expectEqualSlices(u8, &.{ 13, 12, 11 }, &buf);
},
}
}

test "writeStructEndian writes packed structs without padding and in correct field order" {
var buf: [3]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const writer = fis.writer();

const PackedStruct = packed struct(u24) { a: u8, b: u8, c: u8 };

try writer.writeStructEndian(PackedStruct{ .a = 11, .b = 12, .c = 13 }, .little);
try testing.expectEqualSlices(u8, &.{ 11, 12, 13 }, &buf);
fis.reset();
try writer.writeStructEndian(PackedStruct{ .a = 11, .b = 12, .c = 13 }, .big);
try testing.expectEqualSlices(u8, &.{ 13, 12, 11 }, &buf);
}

0 comments on commit 04a383b

Please sign in to comment.