Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support packed structs in std.io.Reader and std.io.Writer #21601

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
47 changes: 38 additions & 9 deletions lib/std/io/Reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -324,20 +324,49 @@ 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, as they have a defined in-memory layout.
/// 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.
kj4tmp marked this conversation as resolved.
Show resolved Hide resolved
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) {
kj4tmp marked this conversation as resolved.
Show resolved Hide resolved
.auto => @compileError("readStruct only supports packed and extern structs, " ++
"but the given type: " ++ @typeName(T) ++ " is a normal struct."),
.@"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, as they have a defined in-memory layout.
/// 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, " ++
"but the given type: " ++ @typeName(T) ++ " is a normal struct."),
.@"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);
kj4tmp marked this conversation as resolved.
Show resolved Hide resolved
}
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
128 changes: 128 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,130 @@ 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.expectEqual(
PackedStruct{ .a = 11, .b = 12, .c = 13 },
reader.readStructEndian(PackedStruct, .little),
);
fis.reset();
try testing.expectEqual(
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.expectEqual(
PackedStruct{ .a = 11, .b = 12, .c = 13 },
reader.readStruct(PackedStruct),
);
},
.big => {
try testing.expectEqual(
PackedStruct{ .a = 13, .b = 12, .c = 11 },
reader.readStruct(PackedStruct),
);
},
}
}

test "readStruct writeStruct round-trip with packed structs" {
var buf: [8]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();
const writer = fis.writer();

const PackedStruct = packed struct(u64) {
a: u16 = 123,
b: u16 = 245,
c: u16 = 456,
d: i13 = -345,
e: i3 = 2,
};

const expected_packed_struct = PackedStruct{};
try writer.writeStruct(expected_packed_struct);
fis.reset();
try testing.expectEqual(expected_packed_struct, try reader.readStruct(PackedStruct));
}

test "readStructEndian writeStructEndian round-trip with packed structs" {
var buf: [8]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();
const writer = fis.writer();

const PackedStruct = packed struct(u64) {
a: u13 = 123,
b: i7 = -24,
c: u20 = 83,
d: enum(i24) { val = 3452 } = .val,
};

const expected_packed_struct = PackedStruct{};
// round-trip little endian
try writer.writeStructEndian(expected_packed_struct, .big);
fis.reset();
try testing.expectEqual(expected_packed_struct, try reader.readStructEndian(PackedStruct, .big));
// round-trip big endian
fis.reset();
try writer.writeStructEndian(expected_packed_struct, .little);
fis.reset();
try testing.expectEqual(expected_packed_struct, try reader.readStructEndian(PackedStruct, .little));
}

test "readStruct a packed struct with endianness-affected types" {
const buf = [4]u8{ 0x12, 0x34, 0x56, 0x78 };
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();

const PackedStruct = packed struct(u32) { a: u16, b: u16 };

switch (native_endian) {
.little => {
try testing.expectEqual(
PackedStruct{ .a = 0x3412, .b = 0x7856 },
reader.readStruct(PackedStruct),
);
},
.big => {
try testing.expectEqual(
PackedStruct{ .a = 0x5678, .b = 0x1234 },
kj4tmp marked this conversation as resolved.
Show resolved Hide resolved
reader.readStruct(PackedStruct),
);
},
}
}

test "readStructEndian a packed struct with endianness-affected types" {
const buf = [4]u8{ 0x12, 0x34, 0x56, 0x78 };
var fis = std.io.fixedBufferStream(&buf);
const reader = fis.reader();

const PackedStruct = packed struct(u32) { a: u16, b: u16 };

try testing.expectEqual(
PackedStruct{ .a = 0x3412, .b = 0x7856 },
reader.readStructEndian(PackedStruct, .little),
);
fis.reset();
try testing.expectEqual(
PackedStruct{ .a = 0x5678, .b = 0x1234 },
reader.readStructEndian(PackedStruct, .big),
);
}
48 changes: 40 additions & 8 deletions lib/std/io/Writer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,48 @@ 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, as they have a defined in-memory layout.
/// 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, " ++
"but the given type: " ++ @typeName(@TypeOf(value)) ++ " is a normal struct."),
.@"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, as they have a defined in-memory layout.
/// 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, " ++
"but the given type: " ++ @typeName(@TypeOf(value)) ++ " is a normal struct."),
.@"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 +109,7 @@ pub fn writeFile(self: Self, file: std.fs.File) anyerror!void {
if (n < buf.len) return;
}
}

test {
_ = @import("Writer/test.zig");
}
63 changes: 63 additions & 0 deletions lib/std/io/Writer/test.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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 };
kj4tmp marked this conversation as resolved.
Show resolved Hide resolved

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);
}

test "writeStruct a packed struct with endianness-affected types" {
var buf: [4]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const writer = fis.writer();

const PackedStruct = packed struct(u32) { a: u16, b: u16 };

try writer.writeStruct(PackedStruct{ .a = 0x1234, .b = 0x5678 });
switch (native_endian) {
.little => try testing.expectEqualSlices(u8, &.{ 0x34, 0x12, 0x78, 0x56 }, &buf),
.big => try testing.expectEqualSlices(u8, &.{ 0x56, 0x78, 0x12, 0x34 }, &buf),
}
}

test "writeStructEndian a packed struct with endianness-affected types" {
var buf: [4]u8 = undefined;
var fis = std.io.fixedBufferStream(&buf);
const writer = fis.writer();

const PackedStruct = packed struct(u32) { a: u16, b: u16 };

try writer.writeStructEndian(PackedStruct{ .a = 0x1234, .b = 0x5678 }, .little);
try testing.expectEqualSlices(u8, &.{ 0x34, 0x12, 0x78, 0x56 }, &buf);
fis.reset();
try writer.writeStructEndian(PackedStruct{ .a = 0x1234, .b = 0x5678 }, .big);
try testing.expectEqualSlices(u8, &.{ 0x56, 0x78, 0x12, 0x34 }, &buf);
}
Loading