authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-06-07 20:55:13-07:00
committergravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-06-07 23:27:00-07:00
logbe07f95cd7e543abdcc06292892eb57ee1b83153
treee17b4868d21c7a85a86d6a3608a0a6873f95f198
parent20b3aa3bcd55ad8f3f545f5ab0f0c2ac7122e2a2

Configuration: fix loadBits/storeBits on big endian architectures

Fixes #35669

2 files changed, 89 insertions(+), 15 deletions(-)

lib/std/Build.zig+1
...@@ -2707,4 +2707,5 @@ pub fn systemIntegrationOption(...@@ -2707,4 +2707,5 @@ pub fn systemIntegrationOption(
2707test {2707test {
2708 _ = Cache;2708 _ = Cache;
2709 _ = Step;2709 _ = Step;
2710 _ = Configuration;
2710}2711}
lib/std/Build/Configuration.zig+88-15
...@@ -1,10 +1,12 @@...@@ -1,10 +1,12 @@
1const Configuration = @This();1const Configuration = @This();
22
3const std = @import("../std.zig");3const std = @import("../std.zig");
4const builtin = @import("builtin");
4const Io = std.Io;5const Io = std.Io;
5const Allocator = std.mem.Allocator;6const Allocator = std.mem.Allocator;
6const assert = std.debug.assert;7const assert = std.debug.assert;
7const max_u32 = std.math.maxInt(u32);8const max_u32 = std.math.maxInt(u32);
9const native_endian = builtin.target.cpu.arch.endian();
810
9string_bytes: []u8,11string_bytes: []u8,
10steps: []Step,12steps: []Step,
...@@ -3433,18 +3435,46 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {...@@ -3433,18 +3435,46 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration {
3433 return result;3435 return result;
3434}3436}
34353437
3438/// Loads bits using native endianness when `value` spans multiple bytes.
3439/// On big endian architectures, `bit_offset` uses MSb 0 bit numbering.
3440/// On little endian architectures, `bit_offset` uses LSb 0 bit numbering.
3441/// See `storeBits`.
3436pub fn loadBits(comptime Int: type, buffer: []const Int, bit_offset: usize, comptime Result: type) Result {3442pub fn loadBits(comptime Int: type, buffer: []const Int, bit_offset: usize, comptime Result: type) Result {
3437 const index = bit_offset / @bitSizeOf(Int);3443 const index = bit_offset / @bitSizeOf(Int);
3438 const small_bit_offset = bit_offset % @bitSizeOf(Int);3444 const small_bit_offset = bit_offset % @bitSizeOf(Int);
3439 const ResultInt = @Int(.unsigned, @bitSizeOf(Result));3445 const ResultInt = @Int(.unsigned, @bitSizeOf(Result));
3440 const result: ResultInt = @truncate(buffer[index] >> @intCast(small_bit_offset));3446 switch (native_endian) {
3441 const available_bits = @bitSizeOf(Int) - small_bit_offset;3447 .little => {
3442 if (available_bits >= @bitSizeOf(ResultInt)) return @bitCast(result);3448 const result: ResultInt = @truncate(buffer[index] >> @intCast(small_bit_offset));
3443 const missing_bits = @bitSizeOf(ResultInt) - available_bits;3449 const available_bits = @bitSizeOf(Int) - small_bit_offset;
3444 const upper: ResultInt = @truncate(buffer[index + 1] & ((@as(usize, 1) << @intCast(missing_bits)) - 1));3450 if (available_bits >= @bitSizeOf(ResultInt)) return @bitCast(result);
3445 return @bitCast(result | (upper << @intCast(available_bits)));3451 const missing_bits = @bitSizeOf(ResultInt) - available_bits;
3452 const upper: ResultInt = @truncate(buffer[index + 1] & ((@as(usize, 1) << @intCast(missing_bits)) - 1));
3453 return @bitCast(result | (upper << @intCast(available_bits)));
3454 },
3455 .big => {
3456 const available_bits = @bitSizeOf(Int) - small_bit_offset;
3457 if (available_bits >= @bitSizeOf(ResultInt)) {
3458 const shift = available_bits - @bitSizeOf(ResultInt);
3459 const result: ResultInt = @truncate(buffer[index] >> @intCast(shift));
3460 return @bitCast(result);
3461 }
3462 const mask = (@as(Int, 1) << @intCast(available_bits)) - 1;
3463 const result: ResultInt = @intCast(buffer[index] & mask);
3464 const missing_bits = @bitSizeOf(ResultInt) - available_bits;
3465 const lower: ResultInt = @truncate(buffer[index + 1] >> @intCast(@bitSizeOf(Int) - missing_bits));
3466 return @bitCast((result << @intCast(missing_bits)) | lower);
3467 },
3468 }
3446}3469}
34473470
3471/// Store bits using native endianness when `value` spans multiple bytes.
3472/// On big endian architectures:
3473/// - For a given value, the bits of an earlier byte are more significant than the bits of subsequent bytes.
3474/// - `bit_offset` uses MSb 0 bit numbering.
3475/// On little endian architectures:
3476/// - For a given value, the bits of an earlier byte are less significant than the bits of subsequent bytes.
3477/// - `bit_offset` uses LSb 0 bit numbering.
3448pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void {3478pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void {
3449 const Value = @TypeOf(value);3479 const Value = @TypeOf(value);
3450 const ValueInt = @Int(.unsigned, @bitSizeOf(Value));3480 const ValueInt = @Int(.unsigned, @bitSizeOf(Value));
...@@ -3453,27 +3483,70 @@ pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: an...@@ -3453,27 +3483,70 @@ pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: an
3453 const small_bit_offset = bit_offset % @bitSizeOf(Int);3483 const small_bit_offset = bit_offset % @bitSizeOf(Int);
3454 const available_bits = @bitSizeOf(Int) - small_bit_offset;3484 const available_bits = @bitSizeOf(Int) - small_bit_offset;
3455 if (available_bits >= @bitSizeOf(ValueInt)) {3485 if (available_bits >= @bitSizeOf(ValueInt)) {
3456 buffer[index] &= ~(((@as(Int, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset));3486 const shift = switch (native_endian) {
3457 buffer[index] |= @as(Int, value_int) << @intCast(small_bit_offset);3487 .little => small_bit_offset,
3488 .big => available_bits - @bitSizeOf(ValueInt),
3489 };
3490 buffer[index] &= ~(((@as(Int, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(shift));
3491 buffer[index] |= @as(Int, value_int) << @intCast(shift);
3458 } else {3492 } else {
3459 const DoubleInt = @Int(.unsigned, @bitSizeOf(Int) * 2);3493 const DoubleInt = @Int(.unsigned, @bitSizeOf(Int) * 2);
3494 const shift = switch (native_endian) {
3495 .little => small_bit_offset,
3496 .big => @bitSizeOf(DoubleInt) - small_bit_offset - @bitSizeOf(ValueInt),
3497 };
3460 const ptr: *align(@alignOf(Int)) DoubleInt = @ptrCast(buffer[index..][0..2]);3498 const ptr: *align(@alignOf(Int)) DoubleInt = @ptrCast(buffer[index..][0..2]);
3461 ptr.* &= ~(((@as(DoubleInt, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset));3499 ptr.* &= ~(((@as(DoubleInt, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(shift));
3462 ptr.* |= @as(DoubleInt, value_int) << @intCast(small_bit_offset);3500 ptr.* |= @as(DoubleInt, value_int) << @intCast(shift);
3463 }3501 }
3464}3502}
34653503
3466test "loadBits and storeBits" {3504test "loadBits and storeBits" {
3467 var buffer: [2]u32 = .{3505 var buffer: [2]u32 = switch (native_endian) {
3468 0b01111111000000001111111100000000,3506 .little => .{
3469 0b11111111000000001111111100000100,3507 //──┐ 0b100011 (end) ┌─┐ 0b100
3508 0b01111111000000001111111100000000,
3509 // n <── bit offset 0 ┘
3510 // ┌── 0b100011 (start)
3511 0b11111111000000001111111100000100,
3512 },
3513 .big => .{
3514 // ┌─┐ 0b100 ┌── 0b100011 (start)
3515 0b11111110000000001111111100000100,
3516 //└ bit offset 0 ──> n
3517 //──┐ 0b100011 (end)
3518 0b01111111000000001111111100000000,
3519 },
3470 };3520 };
3521
3471 try std.testing.expectEqual(0b100, loadBits(u32, &buffer, 6, u3));3522 try std.testing.expectEqual(0b100, loadBits(u32, &buffer, 6, u3));
3472 try std.testing.expectEqual(0b100011, loadBits(u32, &buffer, 29, u6));3523 try std.testing.expectEqual(0b100011, loadBits(u32, &buffer, 29, u6));
34733524
3525 storeBits(u32, &buffer, 0, @as(u1, 0b0));
3474 storeBits(u32, &buffer, 6, @as(u3, 0b010));3526 storeBits(u32, &buffer, 6, @as(u3, 0b010));
3475 storeBits(u32, &buffer, 29, @as(u6, 0b010010));3527 storeBits(u32, &buffer, 29, @as(u6, 0b010110));
3528 storeBits(u32, &buffer, 40, @as(u17, 0b01110110011111110));
34763529
3530 try std.testing.expectEqual(0b0, loadBits(u32, &buffer, 0, u1));
3477 try std.testing.expectEqual(0b010, loadBits(u32, &buffer, 6, u3));3531 try std.testing.expectEqual(0b010, loadBits(u32, &buffer, 6, u3));
3478 try std.testing.expectEqual(0b010010, loadBits(u32, &buffer, 29, u6));3532 try std.testing.expectEqual(0b010110, loadBits(u32, &buffer, 29, u6));
3533 try std.testing.expectEqual(0b01110110011111110, loadBits(u32, &buffer, 40, u17));
3534
3535 // Test roundtripping of size/offset combinations
3536 inline for (1..32) |value_size| {
3537 for (0..64) |bit_offset| {
3538 if (value_size + bit_offset > @bitSizeOf(@TypeOf(buffer))) continue;
3539
3540 buffer = .{ 0, 0 };
3541
3542 const Value = @Int(.unsigned, value_size);
3543 const value: Value = @intCast((@as(u32, 1) << @intCast(@bitSizeOf(Value))) - 1);
3544 storeBits(u32, &buffer, bit_offset, value);
3545 std.testing.expectEqual(value, loadBits(u32, &buffer, bit_offset, Value)) catch |err| {
3546 std.debug.print("value size: {} bit offset: {}\n", .{ value_size, bit_offset });
3547 std.debug.print("buffer: {b:0>32} {b:0>32}\n", .{ buffer[0], buffer[1] });
3548 return err;
3549 };
3550 }
3551 }
3479}3552}