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)...@@ -1094,33 +1094,41 @@ pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian)
1094 return std.mem.readInt(T, try r.takeArray(n), endian);1094 return std.mem.readInt(T, try r.takeArray(n), endian);
1095}1095}
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
1097/// Asserts the buffer was initialized with a capacity at least `n`.1103/// Asserts the buffer was initialized with a capacity at least `n`.
1098pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {1104pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
1099 assert(n <= @sizeOf(Int));1105 assert(n <= @sizeOf(Int));
1100 return std.mem.readVarInt(Int, try r.take(n), endian);1106 return std.mem.readVarInt(Int, try r.take(n), endian);
1101}1107}
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.
1104///1111///
1105/// Advances the seek position.1112/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1106///1113///
1107/// See also:1114/// See also:
1108/// * `peekStruct`1115/// * `peekStructReference`
1109/// * `takeStructEndian`1116/// * `takeStruct`
1110pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {1117pub fn takeStructReference(r: *Reader, comptime T: type) Error!*align(1) T {
1111 // Only extern and packed structs have defined in-memory layout.1118 // Only extern and packed structs have defined in-memory layout.
1112 comptime assert(@typeInfo(T).@"struct".layout != .auto);1119 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1113 return @ptrCast(try r.takeArray(@sizeOf(T)));1120 return @ptrCast(try r.takeArray(@sizeOf(T)));
1114}1121}
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.
1117///1125///
1118/// Does not advance the seek position.1126/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1119///1127///
1120/// See also:1128/// See also:
1121/// * `takeStruct`1129/// * `takeStructReference`
1122/// * `peekStructEndian`1130/// * `peekStruct`
1123pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {1131pub fn peekStructReference(r: *Reader, comptime T: type) Error!*align(1) T {
1124 // Only extern and packed structs have defined in-memory layout.1132 // Only extern and packed structs have defined in-memory layout.
1125 comptime assert(@typeInfo(T).@"struct".layout != .auto);1133 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1126 return @ptrCast(try r.peekArray(@sizeOf(T)));1134 return @ptrCast(try r.peekArray(@sizeOf(T)));
...@@ -1132,12 +1140,23 @@ pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {...@@ -1132,12 +1140,23 @@ pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1132/// when `endian` is comptime-known and matches the host endianness.1140/// when `endian` is comptime-known and matches the host endianness.
1133///1141///
1134/// See also:1142/// See also:
1135/// * `takeStruct`1143/// * `takeStructReference`
1136/// * `peekStructEndian`1144/// * `peekStruct`
1137pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {1145pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1138 var res = (try r.takeStruct(T)).*;1146 switch (@typeInfo(T)) {
1139 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);1147 .@"struct" => |info| switch (info.layout) {
1140 return res;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 }
1141}1160}
11421161
1143/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.1162/// 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...@@ -1146,12 +1165,23 @@ pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin
1146/// when `endian` is comptime-known and matches the host endianness.1165/// when `endian` is comptime-known and matches the host endianness.
1147///1166///
1148/// See also:1167/// See also:
1149/// * `takeStructEndian`1168/// * `takeStruct`
1150/// * `peekStruct`1169/// * `peekStructReference`
1151pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {1170pub inline fn peekStruct(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1152 var res = (try r.peekStruct(T)).*;1171 switch (@typeInfo(T)) {
1153 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);1172 .@"struct" => |info| switch (info.layout) {
1154 return res;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 }
1155}1185}
11561186
1157pub const TakeEnumError = Error || error{InvalidEnumTag};1187pub const TakeEnumError = Error || error{InvalidEnumTag};
...@@ -1517,43 +1547,43 @@ test takeVarInt {...@@ -1517,43 +1547,43 @@ test takeVarInt {
1517 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));1547 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
1518}1548}
15191549
1520test takeStruct {1550test takeStructReference {
1521 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });1551 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1522 const S = extern struct { a: u8, b: u16 };1552 const S = extern struct { a: u8, b: u16 };
1523 switch (native_endian) {1553 switch (native_endian) {
1524 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),1554 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStructReference(S)).*),
1525 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),1555 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStructReference(S)).*),
1526 }1556 }
1527 try testing.expectError(error.EndOfStream, r.takeStruct(S));1557 try testing.expectError(error.EndOfStream, r.takeStructReference(S));
1528}1558}
15291559
1530test peekStruct {1560test peekStructReference {
1531 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });1561 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1532 const S = extern struct { a: u8, b: u16 };1562 const S = extern struct { a: u8, b: u16 };
1533 switch (native_endian) {1563 switch (native_endian) {
1534 .little => {1564 .little => {
1535 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)).*);
1536 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);1566 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStructReference(S)).*);
1537 },1567 },
1538 .big => {1568 .big => {
1539 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)).*);
1540 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);1570 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStructReference(S)).*);
1541 },1571 },
1542 }1572 }
1543}1573}
15441574
1545test takeStructEndian {1575test takeStruct {
1546 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });1576 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1547 const S = extern struct { a: u8, b: u16 };1577 const S = extern struct { a: u8, b: u16 };
1548 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));1578 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStruct(S, .big));
1549 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));1579 try testing.expectError(error.EndOfStream, r.takeStruct(S, .little));
1550}1580}
15511581
1552test peekStructEndian {1582test peekStruct {
1553 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });1583 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1554 const S = extern struct { a: u8, b: u16 };1584 const S = extern struct { a: u8, b: u16 };
1555 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));1585 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStruct(S, .big));
1556 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));1586 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStruct(S, .little));
1557}1587}
15581588
1559test takeEnum {1589test 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...@@ -796,16 +796,9 @@ pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.built
796 return w.writeAll(&bytes);796 return w.writeAll(&bytes);
797}797}
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
805/// The function is inline to avoid the dead code in case `endian` is799/// The function is inline to avoid the dead code in case `endian` is
806/// comptime-known and matches host endianness.800/// comptime-known and matches host endianness.
807/// TODO: make sure this value is not a reference type801pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
808pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
809 switch (@typeInfo(@TypeOf(value))) {802 switch (@typeInfo(@TypeOf(value))) {
810 .@"struct" => |info| switch (info.layout) {803 .@"struct" => |info| switch (info.layout) {
811 .auto => @compileError("ill-defined memory layout"),804 .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 {...@@ -2809,7 +2809,7 @@ pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
2809 var buffer: [2000]u8 = undefined;2809 var buffer: [2000]u8 = undefined;
2810 var file_reader = cache_file.reader(&buffer);2810 var file_reader = cache_file.reader(&buffer);
2811 return result: {2811 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;
2813 break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);2813 break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);
2814 } catch |err| switch (err) {2814 } catch |err| switch (err) {
2815 error.ReadFailed => return file_reader.err.?,2815 error.ReadFailed => return file_reader.err.?,
src/Zcu/PerThread.zig+1-1
...@@ -349,7 +349,7 @@ fn loadZirZoirCache(...@@ -349,7 +349,7 @@ fn loadZirZoirCache(
349 const cache_br = &cache_fr.interface;349 const cache_br = &cache_fr.interface;
350350
351 // First we read the header to determine the lengths of arrays.351 // 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) {
353 error.ReadFailed => return cache_fr.err.?,353 error.ReadFailed => return cache_fr.err.?,
354 // This can happen if Zig bails out of this function between creating354 // This can happen if Zig bails out of this function between creating
355 // the cached file and writing it.355 // the cached file and writing it.