179 lines
6.6 KiB
Zig
179 lines
6.6 KiB
Zig
//! A fixed-width integer: a two's complement bit pattern plus the type that says
|
|
//! how to read it.
|
|
//!
|
|
//! Programmer mode computes on patterns of a chosen width rather than on the exact
|
|
//! rationals `number.zig` provides, and standard mode drops into the same
|
|
//! representation for the bitwise operators (FR-2.12). This file is that
|
|
//! representation; `bitwise.zig` is the operations on it.
|
|
//!
|
|
//! There used to be three shapes of the same idea: `Integer{raw, bit_width,
|
|
//! signedness}`, `ProgrammerConfig{bit_width, signedness, display_endian}`, and a
|
|
//! `Domain{bit_width, signedness}` inside `bitwise.zig`. `IntType` is the one
|
|
//! concept each was carrying a copy of: a value pairs it with bits, and a
|
|
//! configuration pairs it with a display preference.
|
|
|
|
const std = @import("std");
|
|
|
|
const Integer = @This();
|
|
|
|
/// The bit pattern. May carry bits above the width; every reader masks.
|
|
raw: u128,
|
|
/// How to read `raw`.
|
|
int_type: IntType,
|
|
|
|
pub fn init(raw: u128, int_type: IntType) Integer {
|
|
return .{ .raw = raw, .int_type = int_type };
|
|
}
|
|
|
|
/// Apply the width mask, truncating to the configured width.
|
|
pub fn masked(self: Integer) u128 {
|
|
return self.raw & self.int_type.mask();
|
|
}
|
|
|
|
/// Interpret as a signed value, sign-extended from the width.
|
|
pub fn signedValue(self: Integer) i128 {
|
|
return self.int_type.signExtend(self.raw);
|
|
}
|
|
|
|
/// Interpret as an unsigned value, which is just the mask.
|
|
pub fn unsignedValue(self: Integer) u128 {
|
|
return self.masked();
|
|
}
|
|
|
|
/// A fixed-width integer type: how wide, and whether the top bit is a sign.
|
|
///
|
|
/// Everything that interprets a pattern needs exactly this pair, which is why it
|
|
/// was being reassembled in three places. The default is 64-bit signed, which is
|
|
/// also standard mode's fixed type.
|
|
pub const IntType = struct {
|
|
width: BitWidth = .bits64,
|
|
signedness: Signedness = .signed,
|
|
|
|
pub fn mask(self: IntType) u128 {
|
|
return self.width.mask();
|
|
}
|
|
|
|
pub fn bits(self: IntType) u8 {
|
|
return self.width.bits();
|
|
}
|
|
|
|
/// The top bit's position, whether or not this type treats it as a sign.
|
|
pub fn topBit(self: IntType) u128 {
|
|
return @as(u128, 1) << @intCast(self.bits() - 1);
|
|
}
|
|
|
|
/// True when the pattern denotes a negative number in this type. An unsigned
|
|
/// type has no negative values, so `>>` is a zero fill there and a large shift
|
|
/// distance is large rather than negative.
|
|
pub fn isNegative(self: IntType, value: u128) bool {
|
|
return self.signedness == .signed and (value & self.mask()) & self.topBit() != 0;
|
|
}
|
|
|
|
/// Sign-extend the pattern to a full i128.
|
|
pub fn signExtend(self: IntType, value: u128) i128 {
|
|
const m = value & self.mask();
|
|
if (self.isNegative(m)) return @bitCast(m | ~self.mask());
|
|
return @intCast(m);
|
|
}
|
|
};
|
|
|
|
/// Configurable integer bit width. The tag is the bit count.
|
|
pub const BitWidth = enum(u8) {
|
|
bits8 = 8,
|
|
bits16 = 16,
|
|
bits32 = 32,
|
|
bits64 = 64,
|
|
bits128 = 128,
|
|
|
|
/// All bits set within the width. Shifts a full mask down, because
|
|
/// `(1 << bits) - 1` overflows at 128.
|
|
pub fn mask(self: BitWidth) u128 {
|
|
const shift: u7 = @intCast(128 - @as(u16, self.bits()));
|
|
return @as(u128, std.math.maxInt(u128)) >> shift;
|
|
}
|
|
|
|
/// Returns the number of bits as a plain integer.
|
|
pub fn bits(self: BitWidth) u8 {
|
|
return @intFromEnum(self);
|
|
}
|
|
};
|
|
|
|
/// Whether the top bit of a pattern is a sign.
|
|
pub const Signedness = enum {
|
|
signed,
|
|
unsigned,
|
|
};
|
|
|
|
// -- Tests --
|
|
|
|
const testing = std.testing;
|
|
|
|
test "BitWidth.mask" {
|
|
try testing.expectEqual(@as(u128, 0xFF), BitWidth.bits8.mask());
|
|
try testing.expectEqual(@as(u128, 0xFFFF), BitWidth.bits16.mask());
|
|
try testing.expectEqual(@as(u128, 0xFFFF_FFFF), BitWidth.bits32.mask());
|
|
try testing.expectEqual(@as(u128, 0xFFFF_FFFF_FFFF_FFFF), BitWidth.bits64.mask());
|
|
try testing.expectEqual(@as(u128, 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF), BitWidth.bits128.mask());
|
|
}
|
|
|
|
test "BitWidth.mask: exactly `bits` low bits are set, at every width" {
|
|
for (std.enums.values(BitWidth)) |bw| {
|
|
try testing.expectEqual(@as(u8, bw.bits()), @popCount(bw.mask()));
|
|
try testing.expectEqual(@as(u128, 1), bw.mask() & 1);
|
|
}
|
|
}
|
|
|
|
test "IntType: signExtend reads the top bit only when the type is signed" {
|
|
const i8_type: IntType = .{ .width = .bits8, .signedness = .signed };
|
|
const u8_type: IntType = .{ .width = .bits8, .signedness = .unsigned };
|
|
|
|
try testing.expectEqual(@as(i128, -1), i8_type.signExtend(0xFF));
|
|
try testing.expectEqual(@as(i128, 255), u8_type.signExtend(0xFF));
|
|
try testing.expectEqual(@as(i128, -128), i8_type.signExtend(0x80));
|
|
try testing.expectEqual(@as(i128, 127), i8_type.signExtend(0x7F));
|
|
|
|
try testing.expect(i8_type.isNegative(0x80));
|
|
try testing.expect(!u8_type.isNegative(0x80));
|
|
|
|
// A 128-bit type has no bits above the width to fill.
|
|
const i128_type: IntType = .{ .width = .bits128, .signedness = .signed };
|
|
try testing.expectEqual(@as(i128, -1), i128_type.signExtend(i128_type.mask()));
|
|
}
|
|
|
|
test "IntType: the default is 64-bit signed, which standard mode uses" {
|
|
const default: IntType = .{};
|
|
try testing.expectEqual(BitWidth.bits64, default.width);
|
|
try testing.expectEqual(Signedness.signed, default.signedness);
|
|
try testing.expectEqual(@as(i128, -1), default.signExtend(default.mask()));
|
|
}
|
|
|
|
test "bits above the width never reach an interpretation" {
|
|
const i8_type: IntType = .{ .width = .bits8 };
|
|
// The high bits are noise from a wider computation.
|
|
try testing.expectEqual(@as(i128, -1), i8_type.signExtend(0xDEAD_00FF));
|
|
try testing.expectEqual(@as(u128, 0xFF), init(0xDEAD_00FF, i8_type).unsignedValue());
|
|
}
|
|
|
|
test "signedValue" {
|
|
const i8_type: IntType = .{ .width = .bits8 };
|
|
try testing.expectEqual(@as(i128, -1), init(0xFF, i8_type).signedValue());
|
|
try testing.expectEqual(@as(i128, 127), init(0x7F, i8_type).signedValue());
|
|
try testing.expectEqual(@as(i128, -128), init(0x80, i8_type).signedValue());
|
|
|
|
const i32_type: IntType = .{ .width = .bits32 };
|
|
try testing.expectEqual(@as(i128, -1), init(0xFFFF_FFFF, i32_type).signedValue());
|
|
}
|
|
|
|
test "unsignedValue masks correctly" {
|
|
const u8_type: IntType = .{ .width = .bits8, .signedness = .unsigned };
|
|
try testing.expectEqual(@as(u128, 0xFF), init(0x1FF, u8_type).unsignedValue());
|
|
}
|
|
|
|
test "the same bits read two ways, which is why the type travels with the value" {
|
|
const bits: u128 = 0xFF;
|
|
try testing.expectEqual(@as(i128, -1), init(bits, .{ .width = .bits8 }).signedValue());
|
|
try testing.expectEqual(
|
|
@as(i128, 255),
|
|
init(bits, .{ .width = .bits8, .signedness = .unsigned }).signedValue(),
|
|
);
|
|
}
|