From d697d97a95688e873d2677367e94010cdaa3ac73 Mon Sep 17 00:00:00 2001 From: Techatrix Date: Sun, 19 Jul 2026 01:10:14 +0200 Subject: [PATCH] std.mem: replace `byteSwapAllFields` with `byteSwap` The attached doc comment claims to only support structs even though the implementation also supports unions, arrays and integers. And unlike `byteSwapAllElements` it doesn't support enums and floats. booleans were only supported when they we're nested in a struct. A check to reject auto layout structs was missing as well. This function is used by the endianness aware functions in Reader and Writer which prevented some arbitrary types from being supported. --- lib/std/Io/Reader.zig | 8 ++-- lib/std/mem.zig | 109 +++++++++++++++++++++++++----------------- 2 files changed, 69 insertions(+), 48 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 68cae96c836d7cab147c327afd06588c118d784c..966af7879c2488a2b8447f6398aa036a79aba263 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -718,7 +718,7 @@ pub inline fn readSliceEndian( endian: std.builtin.Endian, ) Error!void { try readSliceAll(r, @ptrCast(buffer)); - if (native_endian != endian) for (buffer) |*elem| std.mem.byteSwapAllFields(Elem, elem); + if (native_endian != endian) std.mem.byteSwapAllElements(Elem, buffer); } pub const ReadAllocError = Error || Allocator.Error; @@ -734,8 +734,7 @@ pub inline fn readSliceEndianAlloc( ) ReadAllocError![]Elem { const dest = try allocator.alloc(Elem, len); errdefer allocator.free(dest); - try readSliceAll(r, @ptrCast(dest)); - if (native_endian != endian) for (dest) |*elem| std.mem.byteSwapAllFields(Elem, elem); + try r.readSliceEndian(Elem, dest, endian); return dest; } @@ -1227,8 +1226,7 @@ pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endia .auto => @compileError("ill-defined memory layout"), .@"extern" => { var res: T = undefined; - try r.readSliceAll(std.mem.asBytes(&res)); - if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); + try r.readSliceEndian(T, (&res)[0..1], endian); return res; }, .@"packed" => { diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 8353c0ca8e209dd1516371926e0fbfd00f874ded..e35e92471f629a738bddf9a590ccaf937166f14d 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -2215,33 +2215,54 @@ test writeVarPackedInt { try testing.expectEqual(T{ .a = 1, .b = value, .c = 4 }, st); } -/// Swap the byte order of all the members of the fields of a struct -/// (Changing their endianness) -pub fn byteSwapAllFields(comptime S: type, ptr: *S) void { - byteSwapAllFieldsAligned(S, .of(S), ptr); +/// Deprecated: use `byteSwap` instead. +pub const byteSwapAllFields = byteSwap; + +/// Deprecated: use `byteSwapAligned` instead. +pub const byteSwapAllFieldsAligned = byteSwapAligned; + +/// Reverses the byte order. +/// Handles structs, unions, arrays, enums, floats, and integers recursively. +/// The order of extern struct fields and array elements remains unchanged and +/// will be byte swapped recursively. +/// Useful for converting between little-endian and big-endian representations. +pub fn byteSwap(comptime S: type, ptr: *S) void { + byteSwapAligned(S, .of(S), ptr); } -/// Swap the byte order of all the members of the fields of a struct -/// (Changing their endianness) -pub fn byteSwapAllFieldsAligned(comptime S: type, comptime a: Alignment, ptr: *align(a.toByteUnits()) S) void { +/// Reverses the byte order. +/// Handles structs, unions, arrays, enums, floats, and integers recursively. +/// The order of extern struct fields and array elements remains unchanged and +/// will be byte swapped recursively. +/// Useful for converting between little-endian and big-endian representations. +pub fn byteSwapAligned( + comptime S: type, + comptime a: Alignment, + ptr: *align(a.toByteUnits()) S, +) void { switch (@typeInfo(S)) { .@"struct" => |@"struct"| { if (@"struct".backing_integer) |Int| { ptr.* = @bitCast(@byteSwap(@as(Int, @bitCast(ptr.*)))); - } else inline for (@"struct".field_types, @"struct".field_names, @"struct".field_attrs) |f_type, f_name, f_attr| { - switch (@typeInfo(f_type)) { - .@"struct" => byteSwapAllFieldsAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)), - .@"union", .array => byteSwapAllFieldsAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)), - .@"enum" => { - @field(ptr, f_name) = @fromBackingInt(@intCast(@byteSwap(@backingInt(@field(ptr, f_name))))); - }, - .bool => {}, - .float => |float| { - @field(ptr, f_name) = @bitCast(@byteSwap(@as(@Int(.unsigned, float.bits), @bitCast(@field(ptr, f_name))))); - }, - else => { - @field(ptr, f_name) = @byteSwap(@field(ptr, f_name)); - }, + } else { + if (@"struct".layout != .@"extern") { + @compileError("byteSwapAligned expects a packed or extern struct"); + } + inline for (@"struct".field_types, @"struct".field_names, @"struct".field_attrs) |f_type, f_name, f_attr| { + switch (@typeInfo(f_type)) { + .@"struct" => byteSwapAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)), + .@"union", .array => byteSwapAligned(f_type, .fromByteUnits(f_attr.@"align" orelse @alignOf(f_type)), &@field(ptr, f_name)), + .@"enum" => { + @field(ptr, f_name) = @fromBackingInt(@byteSwap(@backingInt(@field(ptr, f_name)))); + }, + .bool => {}, + .float => |float| { + @field(ptr, f_name) = @bitCast(@byteSwap(@as(@Int(.unsigned, float.bits), @bitCast(@field(ptr, f_name))))); + }, + else => { + @field(ptr, f_name) = @byteSwap(@field(ptr, f_name)); + }, + } } } }, @@ -2249,7 +2270,7 @@ pub fn byteSwapAllFieldsAligned(comptime S: type, comptime a: Alignment, ptr: *a ptr.* = @bitCast(@byteSwap(@as(Int, @bitCast(ptr.*)))); } else { if (@"union".layout != .@"extern") { - @compileError("byteSwapAllFields expects a packed or extern union"); + @compileError("byteSwapAligned expects a packed or extern union"); } const first_size = @bitSizeOf(@"union".field_types[0]); @@ -2266,13 +2287,21 @@ pub fn byteSwapAllFieldsAligned(comptime S: type, comptime a: Alignment, ptr: *a .array => |array| { byteSwapAllElements(array.child, ptr); }, + .@"enum" => { + ptr.* = @fromBackingInt(@byteSwap(@backingInt(ptr.*))); + }, + .bool => {}, + .float => |float| { + const int_repr: @Int(.unsigned, float.bits) = @bitCast(ptr.*); + ptr.* = @bitCast(@byteSwap(int_repr)); + }, else => { ptr.* = @byteSwap(ptr.*); }, } } -test byteSwapAllFields { +test byteSwap { const T = extern struct { f0: u8, f1: u16, @@ -2304,6 +2333,9 @@ test byteSwapAllFields { } align(4), f2: u32, }; + const E = enum(u32) { + _, + }; var s = T{ .f0 = 0x12, .f1 = 0x1234, @@ -2327,10 +2359,14 @@ test byteSwapAllFields { .f1 = .{ .f0 = 0x123456789ABCDEF0 }, .f2 = 0x87654321, }; - byteSwapAllFields(T, &s); - byteSwapAllFields(K, &k); - byteSwapAllFields(P, &p); - byteSwapAllFields(A, &a); + var e: E = @fromBackingInt(0x12345678); + var f: f32 = @bitCast(@as(u32, 0x4640e400)); + byteSwap(T, &s); + byteSwap(K, &k); + byteSwap(P, &p); + byteSwap(A, &a); + byteSwap(E, &e); + byteSwap(f32, &f); try std.testing.expectEqual(T{ .f0 = 0x12, .f1 = 0x3412, @@ -2354,28 +2390,15 @@ test byteSwapAllFields { .f1 = .{ .f0 = 0xF0DEBC9A78563412 }, .f2 = 0x21436587, }, a); + try std.testing.expectEqual(@as(E, @fromBackingInt(0x78563412)), e); + try std.testing.expectEqual(@as(f32, @bitCast(@as(u32, 0x00e44046))), f); } /// Reverses the byte order of all elements in a slice. /// Handles structs, unions, arrays, enums, floats, and integers recursively. /// Useful for converting between little-endian and big-endian representations. pub fn byteSwapAllElements(comptime Elem: type, slice: []Elem) void { - for (slice) |*elem| { - switch (@typeInfo(@TypeOf(elem.*))) { - .@"struct", .@"union", .array => byteSwapAllFields(@TypeOf(elem.*), elem), - .@"enum" => { - elem.* = @fromBackingInt(@intCast(@byteSwap(@backingInt(elem.*)))); - }, - .bool => {}, - .float => |float| { - const int_repr: @Int(.unsigned, float.bits) = @bitCast(elem.*); - elem.* = @bitCast(@byteSwap(int_repr)); - }, - else => { - elem.* = @byteSwap(elem.*); - }, - } - } + for (slice) |*elem| byteSwap(Elem, elem); } /// Returns an iterator that iterates over the slices of `buffer` that are not -- 2.54.0