| ... | @@ -1,10 +1,12 @@ | ... | @@ -1,10 +1,12 @@ |
| 1 | const Configuration = @This(); | 1 | const Configuration = @This(); |
| 2 | | 2 | |
| 3 | const std = @import("../std.zig"); | 3 | const std = @import("../std.zig"); |
| | 4 | const builtin = @import("builtin"); |
| 4 | const Io = std.Io; | 5 | const Io = std.Io; |
| 5 | const Allocator = std.mem.Allocator; | 6 | const Allocator = std.mem.Allocator; |
| 6 | const assert = std.debug.assert; | 7 | const assert = std.debug.assert; |
| 7 | const max_u32 = std.math.maxInt(u32); | 8 | const max_u32 = std.math.maxInt(u32); |
| | 9 | const native_endian = builtin.target.cpu.arch.endian(); |
| 8 | | 10 | |
| 9 | string_bytes: []u8, | 11 | string_bytes: []u8, |
| 10 | steps: []Step, | 12 | steps: []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 | } |
| 3435 | | 3437 | |
| | 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`. |
| 3436 | pub fn loadBits(comptime Int: type, buffer: []const Int, bit_offset: usize, comptime Result: type) Result { | 3442 | pub 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 | } |
| 3447 | | 3470 | |
| | 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. |
| 3448 | pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void { | 3478 | pub 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 | } |
| 3465 | | 3503 | |
| 3466 | test "loadBits and storeBits" { | 3504 | test "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)); |
| 3473 | | 3524 | |
| | 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)); |
| 3476 | | 3529 | |
| | 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 | } |