307 lines
11 KiB
Zig
307 lines
11 KiB
Zig
![]() |
const std = @import("std");
|
||
|
const builtin = @import("builtin");
|
||
|
const testing = std.testing;
|
||
|
|
||
|
///
|
||
|
/// A bitmap that uses a specific type to store the entries.
|
||
|
///
|
||
|
/// Arguments:
|
||
|
/// IN BitmapType: type - The integer type to use to store entries.
|
||
|
///
|
||
|
/// Return: type.
|
||
|
/// The bitmap type created.
|
||
|
///
|
||
|
pub fn Bitmap(comptime BitmapType: type) type {
|
||
|
return struct {
|
||
|
/// The possible errors thrown by bitmap functions
|
||
|
pub const BitmapError = error{
|
||
|
/// The address given was outside the region covered by a bitmap
|
||
|
OutOfBounds,
|
||
|
};
|
||
|
|
||
|
const Self = @This();
|
||
|
|
||
|
/// The number of entries that one bitmap type can hold. Evaluates to the number of bits the type has
|
||
|
pub const ENTRIES_PER_BITMAP: u32 = std.meta.bitCount(BitmapType);
|
||
|
|
||
|
/// The value that a full bitmap will have
|
||
|
pub const BITMAP_FULL = std.math.maxInt(BitmapType);
|
||
|
|
||
|
/// The type of an index into a bitmap entry. The smallest integer needed to represent all bit positions in the bitmap entry type
|
||
|
pub const IndexType = @Type(builtin.TypeInfo{ .Int = builtin.TypeInfo.Int{ .is_signed = false, .bits = std.math.log2(std.meta.bitCount(BitmapType)) } });
|
||
|
|
||
|
num_bitmaps: u32,
|
||
|
num_entries: u32,
|
||
|
bitmaps: []BitmapType,
|
||
|
num_free_entries: u32,
|
||
|
|
||
|
///
|
||
|
/// Create an instance of this bitmap type.
|
||
|
///
|
||
|
/// Arguments:
|
||
|
/// IN num_entries: u32 - The number of entries that the bitmap created will have.
|
||
|
/// The number of BitmapType required to store this many entries will be allocated and each will be zeroed.
|
||
|
/// IN allocator: *std.mem.Allocator - The allocator to use when allocating the BitmapTypes required.
|
||
|
///
|
||
|
/// Return: Self.
|
||
|
/// The bitmap instance.
|
||
|
///
|
||
|
/// Error: std.mem.Allocator.Error
|
||
|
/// OutOfMemory: There isn't enough memory available to allocate the required number of BitmapType.
|
||
|
///
|
||
|
pub fn init(num_entries: u32, allocator: *std.mem.Allocator) !Self {
|
||
|
const num = @floatToInt(u32, @ceil(@intToFloat(f32, num_entries) / @intToFloat(f32, ENTRIES_PER_BITMAP)));
|
||
|
const self = Self{
|
||
|
.num_bitmaps = num,
|
||
|
.num_entries = num_entries,
|
||
|
.bitmaps = try allocator.alloc(BitmapType, num),
|
||
|
.num_free_entries = num_entries,
|
||
|
};
|
||
|
for (self.bitmaps) |*bmp| {
|
||
|
bmp.* = 0;
|
||
|
}
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Set an entry within a bitmap as occupied.
|
||
|
///
|
||
|
/// Arguments:
|
||
|
/// INOUT self: *Self - The bitmap to modify.
|
||
|
/// IN idx: BitmapIndex - The index within the bitmap to set.
|
||
|
///
|
||
|
/// Error: BitmapError.
|
||
|
/// OutOfBounds: The index given is out of bounds.
|
||
|
///
|
||
|
pub fn setEntry(self: *Self, idx: u32) BitmapError!void {
|
||
|
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
|
||
|
if (!try self.isSet(idx)) {
|
||
|
const bit = self.indexToBit(idx);
|
||
|
self.bitmaps[idx / ENTRIES_PER_BITMAP] |= bit;
|
||
|
self.num_free_entries -= 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Set an entry within a bitmap as unoccupied.
|
||
|
///
|
||
|
/// Arguments:
|
||
|
/// INOUT self: *Self - The bitmap to modify.
|
||
|
/// IN idx: BitmapIndex - The index within the bitmap to clear.
|
||
|
///
|
||
|
/// Error: BitmapError.
|
||
|
/// OutOfBounds: The index given is out of bounds.
|
||
|
///
|
||
|
pub fn clearEntry(self: *Self, idx: u32) BitmapError!void {
|
||
|
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
|
||
|
if (try self.isSet(idx)) {
|
||
|
const bit = self.indexToBit(idx);
|
||
|
self.bitmaps[idx / ENTRIES_PER_BITMAP] &= ~bit;
|
||
|
self.num_free_entries += 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Convert a global bitmap index into the bit corresponding to an entry within a single BitmapType.
|
||
|
///
|
||
|
/// Arguments:
|
||
|
/// IN self: *Self - The bitmap to use.
|
||
|
/// IN idx: u32 - The index into all of the bitmap's entries.
|
||
|
///
|
||
|
/// Return: BitmapType.
|
||
|
/// The bit corresponding to that index but within a single BitmapType.
|
||
|
///
|
||
|
fn indexToBit(self: *Self, idx: u32) BitmapType {
|
||
|
return @as(BitmapType, 1) << @intCast(IndexType, idx % ENTRIES_PER_BITMAP);
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Set the first free entry within the bitmaps as occupied.
|
||
|
///
|
||
|
/// Return: ?u32.
|
||
|
/// The index within all bitmaps that was set or null if there wasn't one free.
|
||
|
/// 0 .. ENTRIES_PER_BITMAP - 1 if in the first bitmap, ENTRIES_PER_BITMAP .. ENTRIES_PER_BITMAP * 2 - 1 if in the second etc.
|
||
|
///
|
||
|
pub fn setFirstFree(self: *Self) ?u32 {
|
||
|
if (self.num_free_entries == 0) return null;
|
||
|
for (self.bitmaps) |*bmp, i| {
|
||
|
if (bmp.* == BITMAP_FULL)
|
||
|
continue;
|
||
|
const bit = @truncate(IndexType, @ctz(BitmapType, ~bmp.*));
|
||
|
const idx = bit + @intCast(u32, i) * ENTRIES_PER_BITMAP;
|
||
|
// Failing here means that the index is outside of the bitmap, so there are no free entries
|
||
|
self.setEntry(idx) catch return null;
|
||
|
return idx;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
///
|
||
|
/// Check if an entry is set.
|
||
|
///
|
||
|
/// Arguments:
|
||
|
/// IN self: *Bitmap - The bitmap to check.
|
||
|
/// IN idx: u32 - The entry to check.
|
||
|
///
|
||
|
/// Return: bool.
|
||
|
/// True if the entry is set, else false.
|
||
|
///
|
||
|
/// Error: BitmapError.
|
||
|
/// OutOfBounds: The index given is out of bounds.
|
||
|
///
|
||
|
pub fn isSet(self: *Self, idx: u32) BitmapError!bool {
|
||
|
if (idx >= self.num_entries) return BitmapError.OutOfBounds;
|
||
|
return (self.bitmaps[idx / ENTRIES_PER_BITMAP] & self.indexToBit(idx)) != 0;
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
|
||
|
test "setEntry" {
|
||
|
var bmp = try Bitmap(u32).init(31, std.heap.direct_allocator);
|
||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||
|
|
||
|
try bmp.setEntry(0);
|
||
|
testing.expectEqual(@as(u32, 1), bmp.bitmaps[0]);
|
||
|
testing.expectEqual(@as(u32, 30), bmp.num_free_entries);
|
||
|
|
||
|
try bmp.setEntry(1);
|
||
|
testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]);
|
||
|
testing.expectEqual(@as(u32, 29), bmp.num_free_entries);
|
||
|
|
||
|
// Repeat setting entry 1 to make sure state doesn't change
|
||
|
try bmp.setEntry(1);
|
||
|
testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]);
|
||
|
testing.expectEqual(@as(u32, 29), bmp.num_free_entries);
|
||
|
|
||
|
testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.setEntry(31));
|
||
|
testing.expectEqual(@as(u32, 29), bmp.num_free_entries);
|
||
|
}
|
||
|
|
||
|
test "clearEntry" {
|
||
|
var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||
|
testing.expectEqual(@as(u32, 32), bmp.num_free_entries);
|
||
|
|
||
|
try bmp.setEntry(0);
|
||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||
|
try bmp.setEntry(1);
|
||
|
testing.expectEqual(@as(u32, 30), bmp.num_free_entries);
|
||
|
testing.expectEqual(@as(u32, 3), bmp.bitmaps[0]);
|
||
|
try bmp.clearEntry(0);
|
||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||
|
testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]);
|
||
|
|
||
|
// Repeat to make sure state doesn't change
|
||
|
try bmp.clearEntry(0);
|
||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||
|
testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]);
|
||
|
|
||
|
// Try clearing an unset entry to make sure state doesn't change
|
||
|
try bmp.clearEntry(2);
|
||
|
testing.expectEqual(@as(u32, 31), bmp.num_free_entries);
|
||
|
testing.expectEqual(@as(u32, 2), bmp.bitmaps[0]);
|
||
|
|
||
|
testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.clearEntry(32));
|
||
|
}
|
||
|
|
||
|
test "setFirstFree multiple bitmaps" {
|
||
|
var bmp = try Bitmap(u8).init(9, std.heap.direct_allocator);
|
||
|
|
||
|
// Allocate the first entry
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 0);
|
||
|
testing.expectEqual(bmp.bitmaps[0], 1);
|
||
|
|
||
|
// Allocate the second entry
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 1);
|
||
|
testing.expectEqual(bmp.bitmaps[0], 3);
|
||
|
|
||
|
// Allocate the entirety of the first bitmap
|
||
|
var entry: u32 = 2;
|
||
|
var expected: u8 = 7;
|
||
|
while (entry < Bitmap(u8).ENTRIES_PER_BITMAP) {
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, entry);
|
||
|
testing.expectEqual(bmp.bitmaps[0], expected);
|
||
|
if (entry + 1 < Bitmap(u8).ENTRIES_PER_BITMAP) {
|
||
|
entry += 1;
|
||
|
expected = expected * 2 + 1;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Try allocating an entry in the next bitmap
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, Bitmap(u8).ENTRIES_PER_BITMAP);
|
||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL);
|
||
|
testing.expectEqual(bmp.bitmaps[1], 1);
|
||
|
|
||
|
// We should no longer be able to allocate any entries
|
||
|
testing.expectEqual(bmp.setFirstFree(), null);
|
||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u8).BITMAP_FULL);
|
||
|
testing.expectEqual(bmp.bitmaps[1], 1);
|
||
|
}
|
||
|
test "setFirstFree" {
|
||
|
var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||
|
|
||
|
// Allocate the first entry
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 0);
|
||
|
testing.expectEqual(bmp.bitmaps[0], 1);
|
||
|
|
||
|
// Allocate the second entry
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, 1);
|
||
|
testing.expectEqual(bmp.bitmaps[0], 3);
|
||
|
|
||
|
// Make all but the MSB occupied and try to allocate it
|
||
|
bmp.bitmaps[0] = Bitmap(u32).BITMAP_FULL & ~@as(u32, 1 << (Bitmap(u32).ENTRIES_PER_BITMAP - 1));
|
||
|
testing.expectEqual(bmp.setFirstFree() orelse unreachable, Bitmap(u32).ENTRIES_PER_BITMAP - 1);
|
||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u32).BITMAP_FULL);
|
||
|
|
||
|
// We should no longer be able to allocate any entries
|
||
|
testing.expectEqual(bmp.setFirstFree(), null);
|
||
|
testing.expectEqual(bmp.bitmaps[0], Bitmap(u32).BITMAP_FULL);
|
||
|
}
|
||
|
|
||
|
test "isSet" {
|
||
|
var bmp = try Bitmap(u32).init(32, std.heap.direct_allocator);
|
||
|
|
||
|
bmp.bitmaps[0] = 1;
|
||
|
// Make sure that only the set entry is considered set
|
||
|
testing.expect(try bmp.isSet(0));
|
||
|
var i: u32 = 1;
|
||
|
while (i < bmp.num_entries) : (i += 1) {
|
||
|
testing.expect(!try bmp.isSet(i));
|
||
|
}
|
||
|
|
||
|
bmp.bitmaps[0] = 3;
|
||
|
testing.expect(try bmp.isSet(0));
|
||
|
testing.expect(try bmp.isSet(1));
|
||
|
i = 2;
|
||
|
while (i < bmp.num_entries) : (i += 1) {
|
||
|
testing.expect(!try bmp.isSet(i));
|
||
|
}
|
||
|
|
||
|
bmp.bitmaps[0] = 11;
|
||
|
testing.expect(try bmp.isSet(0));
|
||
|
testing.expect(try bmp.isSet(1));
|
||
|
testing.expect(!try bmp.isSet(2));
|
||
|
testing.expect(try bmp.isSet(3));
|
||
|
i = 4;
|
||
|
while (i < bmp.num_entries) : (i += 1) {
|
||
|
testing.expect(!try bmp.isSet(i));
|
||
|
}
|
||
|
|
||
|
testing.expectError(Bitmap(u32).BitmapError.OutOfBounds, bmp.isSet(33));
|
||
|
}
|
||
|
|
||
|
test "indexToBit" {
|
||
|
var bmp = try Bitmap(u8).init(10, std.heap.direct_allocator);
|
||
|
testing.expectEqual(bmp.indexToBit(0), 1);
|
||
|
testing.expectEqual(bmp.indexToBit(1), 2);
|
||
|
testing.expectEqual(bmp.indexToBit(2), 4);
|
||
|
testing.expectEqual(bmp.indexToBit(3), 8);
|
||
|
testing.expectEqual(bmp.indexToBit(4), 16);
|
||
|
testing.expectEqual(bmp.indexToBit(5), 32);
|
||
|
testing.expectEqual(bmp.indexToBit(6), 64);
|
||
|
testing.expectEqual(bmp.indexToBit(7), 128);
|
||
|
testing.expectEqual(bmp.indexToBit(8), 1);
|
||
|
testing.expectEqual(bmp.indexToBit(9), 2);
|
||
|
}
|