authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-15 13:43:08+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-07-15 13:43:08+02:00
loga5dbb656b1271a51462005162a014576cf5260c1
treec24b5508afcbf8bab0c6807c88603f2c2008c17d
parentd3cd0b27145fb9d295078a6883f9714769cb482b
parentdeb9f3e88ff309da542f4fd5063f1f1a05cae27d
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #24454 from ziglang/packed-struct-streams

std.Io: handle packed structs better

5 files changed, 75 insertions(+), 47 deletions(-)

lib/std/Io/Reader.zig+67-37
......@@ -1096,33 +1096,41 @@ pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian)
10961096 return std.mem.readInt(T, try r.takeArray(n), endian);
10971097}
10981098
1099/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
1100pub inline fn peekInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1101 const n = @divExact(@typeInfo(T).int.bits, 8);
1102 return std.mem.readInt(T, try r.peekArray(n), endian);
1103}
1104
10991105/// Asserts the buffer was initialized with a capacity at least `n`.
11001106pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
11011107 assert(n <= @sizeOf(Int));
11021108 return std.mem.readVarInt(Int, try r.take(n), endian);
11031109}
11041110
1105/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1111/// Obtains an unaligned pointer to the beginning of the stream, reinterpreted
1112/// as a pointer to the provided type, advancing the seek position.
11061113///
1107/// Advances the seek position.
1114/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
11081115///
11091116/// See also:
1110/// * `peekStruct`
1111/// * `takeStructEndian`
1112pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1117/// * `peekStructReference`
1118/// * `takeStruct`
1119pub fn takeStructReference(r: *Reader, comptime T: type) Error!*align(1) T {
11131120 // Only extern and packed structs have defined in-memory layout.
11141121 comptime assert(@typeInfo(T).@"struct".layout != .auto);
11151122 return @ptrCast(try r.takeArray(@sizeOf(T)));
11161123}
11171124
1118/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1125/// Obtains an unaligned pointer to the beginning of the stream, reinterpreted
1126/// as a pointer to the provided type, without advancing the seek position.
11191127///
1120/// Does not advance the seek position.
1128/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
11211129///
11221130/// See also:
1123/// * `takeStruct`
1124/// * `peekStructEndian`
1125pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1131/// * `takeStructReference`
1132/// * `peekStruct`
1133pub fn peekStructReference(r: *Reader, comptime T: type) Error!*align(1) T {
11261134 // Only extern and packed structs have defined in-memory layout.
11271135 comptime assert(@typeInfo(T).@"struct".layout != .auto);
11281136 return @ptrCast(try r.peekArray(@sizeOf(T)));
......@@ -1134,12 +1142,23 @@ pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
11341142/// when `endian` is comptime-known and matches the host endianness.
11351143///
11361144/// See also:
1137/// * `takeStruct`
1138/// * `peekStructEndian`
1139pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1140 var res = (try r.takeStruct(T)).*;
1141 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1142 return res;
1145/// * `takeStructReference`
1146/// * `peekStruct`
1147pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1148 switch (@typeInfo(T)) {
1149 .@"struct" => |info| switch (info.layout) {
1150 .auto => @compileError("ill-defined memory layout"),
1151 .@"extern" => {
1152 var res = (try r.takeStructReference(T)).*;
1153 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1154 return res;
1155 },
1156 .@"packed" => {
1157 return takeInt(r, info.backing_integer.?, endian);
1158 },
1159 },
1160 else => @compileError("not a struct"),
1161 }
11431162}
11441163
11451164/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
......@@ -1148,12 +1167,23 @@ pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin
11481167/// when `endian` is comptime-known and matches the host endianness.
11491168///
11501169/// See also:
1151/// * `takeStructEndian`
1152/// * `peekStruct`
1153pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1154 var res = (try r.peekStruct(T)).*;
1155 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1156 return res;
1170/// * `takeStruct`
1171/// * `peekStructReference`
1172pub inline fn peekStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1173 switch (@typeInfo(T)) {
1174 .@"struct" => |info| switch (info.layout) {
1175 .auto => @compileError("ill-defined memory layout"),
1176 .@"extern" => {
1177 var res = (try r.peekStructReference(T)).*;
1178 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1179 return res;
1180 },
1181 .@"packed" => {
1182 return peekInt(r, info.backing_integer.?, endian);
1183 },
1184 },
1185 else => @compileError("not a struct"),
1186 }
11571187}
11581188
11591189pub const TakeEnumError = Error || error{InvalidEnumTag};
......@@ -1519,43 +1549,43 @@ test takeVarInt {
15191549 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
15201550}
15211551
1522test takeStruct {
1552test takeStructReference {
15231553 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15241554 const S = extern struct { a: u8, b: u16 };
15251555 switch (native_endian) {
1526 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),
1527 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),
1556 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStructReference(S)).*),
1557 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStructReference(S)).*),
15281558 }
1529 try testing.expectError(error.EndOfStream, r.takeStruct(S));
1559 try testing.expectError(error.EndOfStream, r.takeStructReference(S));
15301560}
15311561
1532test peekStruct {
1562test peekStructReference {
15331563 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15341564 const S = extern struct { a: u8, b: u16 };
15351565 switch (native_endian) {
15361566 .little => {
1537 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1538 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1567 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*);
1568 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*);
15391569 },
15401570 .big => {
1541 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1542 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1571 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*);
1572 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*);
15431573 },
15441574 }
15451575}
15461576
1547test takeStructEndian {
1577test takeStruct {
15481578 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15491579 const S = extern struct { a: u8, b: u16 };
1550 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));
1551 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));
1580 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStruct(S, .big));
1581 try testing.expectError(error.EndOfStream, r.takeStruct(S, .little));
15521582}
15531583
1554test peekStructEndian {
1584test peekStruct {
15551585 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15561586 const S = extern struct { a: u8, b: u16 };
1557 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));
1558 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));
1587 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStruct(S, .big));
1588 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStruct(S, .little));
15591589}
15601590
15611591test takeEnum {
lib/std/Io/Writer.zig+1-8
......@@ -796,16 +796,9 @@ pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.built
796796 return w.writeAll(&bytes);
797797}
798798
799pub fn writeStruct(w: *Writer, value: anytype) Error!void {
800 // Only extern and packed structs have defined in-memory layout.
801 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
802 return w.writeAll(std.mem.asBytes(&value));
803}
804
805799/// The function is inline to avoid the dead code in case `endian` is
806800/// comptime-known and matches host endianness.
807/// TODO: make sure this value is not a reference type
808pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
801pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
809802 switch (@typeInfo(@TypeOf(value))) {
810803 .@"struct" => |info| switch (info.layout) {
811804 .auto => @compileError("ill-defined memory layout"),
lib/std/posix/test.zig+5
......@@ -1001,6 +1001,11 @@ test "sigset_t bits" {
10011001 if (native_os == .wasi or native_os == .windows)
10021002 return error.SkipZigTest;
10031003
1004 if (true) {
1005 // https://github.com/ziglang/zig/issues/24380
1006 return error.SkipZigTest;
1007 }
1008
10041009 const S = struct {
10051010 var expected_sig: i32 = undefined;
10061011 var handler_called_count: u32 = 0;
src/Zcu.zig+1-1
......@@ -2809,7 +2809,7 @@ pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
28092809 var buffer: [2000]u8 = undefined;
28102810 var file_reader = cache_file.reader(&buffer);
28112811 return result: {
2812 const header = file_reader.interface.takeStruct(Zir.Header) catch |err| break :result err;
2812 const header = file_reader.interface.takeStructReference(Zir.Header) catch |err| break :result err;
28132813 break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);
28142814 } catch |err| switch (err) {
28152815 error.ReadFailed => return file_reader.err.?,
src/Zcu/PerThread.zig+1-1
......@@ -349,7 +349,7 @@ fn loadZirZoirCache(
349349 const cache_br = &cache_fr.interface;
350350
351351 // First we read the header to determine the lengths of arrays.
352 const header = (cache_br.takeStruct(Header) catch |err| switch (err) {
352 const header = (cache_br.takeStructReference(Header) catch |err| switch (err) {
353353 error.ReadFailed => return cache_fr.err.?,
354354 // This can happen if Zig bails out of this function between creating
355355 // the cached file and writing it.