authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-14 18:37:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-14 18:43:56-07:00
logdeb9f3e88ff309da542f4fd5063f1f1a05cae27d
treeeb3ac0c2bad28e850926f477c99df9fc898792df
parent34d7cf075e831c2f2a078f85513401c8559748cf

std.Io: handle packed structs better

Rather than having the endian-suffixed functions be the preferred ones the unsuffixed ones are the preferred ones and the tricky functions get a special suffix. Makes packed structs read and written the same as integers. closes #12960

4 files changed, 70 insertions(+), 47 deletions(-)

lib/std/Io/Reader.zig+67-37
......@@ -1094,33 +1094,41 @@ pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian)
10941094 return std.mem.readInt(T, try r.takeArray(n), endian);
10951095}
10961096
1097/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
1098pub inline fn peekInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1099 const n = @divExact(@typeInfo(T).int.bits, 8);
1100 return std.mem.readInt(T, try r.peekArray(n), endian);
1101}
1102
10971103/// Asserts the buffer was initialized with a capacity at least `n`.
10981104pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
10991105 assert(n <= @sizeOf(Int));
11001106 return std.mem.readVarInt(Int, try r.take(n), endian);
11011107}
11021108
1103/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1109/// Obtains an unaligned pointer to the beginning of the stream, reinterpreted
1110/// as a pointer to the provided type, advancing the seek position.
11041111///
1105/// Advances the seek position.
1112/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
11061113///
11071114/// See also:
1108/// * `peekStruct`
1109/// * `takeStructEndian`
1110pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1115/// * `peekStructReference`
1116/// * `takeStruct`
1117pub fn takeStructReference(r: *Reader, comptime T: type) Error!*align(1) T {
11111118 // Only extern and packed structs have defined in-memory layout.
11121119 comptime assert(@typeInfo(T).@"struct".layout != .auto);
11131120 return @ptrCast(try r.takeArray(@sizeOf(T)));
11141121}
11151122
1116/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1123/// Obtains an unaligned pointer to the beginning of the stream, reinterpreted
1124/// as a pointer to the provided type, without advancing the seek position.
11171125///
1118/// Does not advance the seek position.
1126/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
11191127///
11201128/// See also:
1121/// * `takeStruct`
1122/// * `peekStructEndian`
1123pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1129/// * `takeStructReference`
1130/// * `peekStruct`
1131pub fn peekStructReference(r: *Reader, comptime T: type) Error!*align(1) T {
11241132 // Only extern and packed structs have defined in-memory layout.
11251133 comptime assert(@typeInfo(T).@"struct".layout != .auto);
11261134 return @ptrCast(try r.peekArray(@sizeOf(T)));
......@@ -1132,12 +1140,23 @@ pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
11321140/// when `endian` is comptime-known and matches the host endianness.
11331141///
11341142/// See also:
1135/// * `takeStruct`
1136/// * `peekStructEndian`
1137pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1138 var res = (try r.takeStruct(T)).*;
1139 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1140 return res;
1143/// * `takeStructReference`
1144/// * `peekStruct`
1145pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1146 switch (@typeInfo(T)) {
1147 .@"struct" => |info| switch (info.layout) {
1148 .auto => @compileError("ill-defined memory layout"),
1149 .@"extern" => {
1150 var res = (try r.takeStructReference(T)).*;
1151 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1152 return res;
1153 },
1154 .@"packed" => {
1155 return takeInt(r, info.backing_integer.?, endian);
1156 },
1157 },
1158 else => @compileError("not a struct"),
1159 }
11411160}
11421161
11431162/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
......@@ -1146,12 +1165,23 @@ pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin
11461165/// when `endian` is comptime-known and matches the host endianness.
11471166///
11481167/// See also:
1149/// * `takeStructEndian`
1150/// * `peekStruct`
1151pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1152 var res = (try r.peekStruct(T)).*;
1153 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1154 return res;
1168/// * `takeStruct`
1169/// * `peekStructReference`
1170pub inline fn peekStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1171 switch (@typeInfo(T)) {
1172 .@"struct" => |info| switch (info.layout) {
1173 .auto => @compileError("ill-defined memory layout"),
1174 .@"extern" => {
1175 var res = (try r.peekStructReference(T)).*;
1176 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1177 return res;
1178 },
1179 .@"packed" => {
1180 return peekInt(r, info.backing_integer.?, endian);
1181 },
1182 },
1183 else => @compileError("not a struct"),
1184 }
11551185}
11561186
11571187pub const TakeEnumError = Error || error{InvalidEnumTag};
......@@ -1517,43 +1547,43 @@ test takeVarInt {
15171547 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
15181548}
15191549
1520test takeStruct {
1550test takeStructReference {
15211551 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15221552 const S = extern struct { a: u8, b: u16 };
15231553 switch (native_endian) {
1524 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),
1525 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),
1554 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStructReference(S)).*),
1555 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStructReference(S)).*),
15261556 }
1527 try testing.expectError(error.EndOfStream, r.takeStruct(S));
1557 try testing.expectError(error.EndOfStream, r.takeStructReference(S));
15281558}
15291559
1530test peekStruct {
1560test peekStructReference {
15311561 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15321562 const S = extern struct { a: u8, b: u16 };
15331563 switch (native_endian) {
15341564 .little => {
1535 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1536 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1565 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*);
1566 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*);
15371567 },
15381568 .big => {
1539 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1540 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1569 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*);
1570 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*);
15411571 },
15421572 }
15431573}
15441574
1545test takeStructEndian {
1575test takeStruct {
15461576 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15471577 const S = extern struct { a: u8, b: u16 };
1548 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));
1549 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));
1578 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStruct(S, .big));
1579 try testing.expectError(error.EndOfStream, r.takeStruct(S, .little));
15501580}
15511581
1552test peekStructEndian {
1582test peekStruct {
15531583 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
15541584 const S = extern struct { a: u8, b: u16 };
1555 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));
1556 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));
1585 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStruct(S, .big));
1586 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStruct(S, .little));
15571587}
15581588
15591589test 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"),
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.