nativity/bootstrap/data_structures.zig
2023-11-20 09:36:04 -06:00

336 lines
12 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
pub const Allocator = std.mem.Allocator;
pub const AutoArrayHashMap = std.AutoArrayHashMapUnmanaged;
pub const ArrayList = std.ArrayListUnmanaged;
pub const ArrayListAligned = std.ArrayListAlignedUnmanaged;
pub const AutoHashMap = std.AutoHashMapUnmanaged;
pub const HashMap = std.HashMapUnmanaged;
pub const SegmentedList = std.SegmentedList;
pub const StringHashMap = std.StringHashMapUnmanaged;
pub const StringArrayHashMap = std.StringArrayHashMapUnmanaged;
pub fn BlockList(comptime T: type) type {
const item_count = 64;
const Block = struct {
items: [item_count]T = undefined,
bitset: Bitset = Bitset.initEmpty(),
const Bitset = std.StaticBitSet(item_count);
fn allocateIndex(block: *@This()) !u6 {
if (block.bitset.mask != std.math.maxInt(@TypeOf(block.bitset.mask))) {
const index = @ctz(~block.bitset.mask);
block.bitset.set(index);
return @intCast(index);
} else {
return error.OutOfMemory;
}
}
};
return struct {
// TODO: make this not reallocate the whole block. Instead, use a pointer to the block as the ArrayList item
blocks: ArrayList(Block) = .{},
len: usize = 0,
first_block: u32 = 0,
const List = @This();
pub const Index = packed struct(u32) {
element: u6,
block: u24,
_reserved: bool = false,
invalid: bool = false,
pub const invalid = Index{
.invalid = true,
.element = 0,
.block = 0,
};
pub fn eq(index: Index, other: Index) bool {
return @as(u32, @bitCast(index)) == @as(u32, @bitCast(other));
}
pub fn uniqueInteger(index: Index) u32 {
assert(!index.invalid);
return @as(u30, @truncate(@as(u32, @bitCast(index))));
}
pub fn fromInteger(usize_index: usize) Index {
const index: u32 = @intCast(usize_index);
const block: u24 = @intCast(index / item_count);
const i: u6 = @intCast(index % item_count);
return .{
.element = i,
.block = block,
};
}
};
pub const Iterator = struct {
index: Index,
list: *List,
pub const Pair = struct {
index: Index,
};
pub fn nextIndex(i: *Iterator) ?Index {
// TODO: optimize with ctz and masking out already iterated indices in the bitmask
for (i.index.block..i.list.blocks.items.len) |block_index| {
for (@as(u8, i.index.element)..item_count) |element_index| {
if (i.list.blocks.items[block_index].bitset.isSet(element_index)) {
const index = Index{
.element = @intCast(element_index),
.block = @intCast(block_index),
};
i.index = index;
i.index.element +%= 1;
i.index.block = @as(u24, @intCast(block_index)) + @intFromBool(i.index.element < element_index);
return index;
}
}
}
return null;
}
pub fn nextPointer(i: *Iterator) ?*T {
if (i.nextIndex()) |index| {
const result = i.list.get(index);
return result;
} else {
return null;
}
}
};
pub const Allocation = struct {
ptr: *T,
index: Index,
};
pub fn iterator(list: *List) Iterator {
return .{
.index = Index{
.element = 0,
.block = 0,
},
.list = list,
};
}
pub fn get(list: *List, index: Index) *T {
assert(!index.invalid);
return &list.blocks.items[index.block].items[index.element];
}
pub fn append(list: *List, allocator: Allocator, element: T) !Allocation {
const result = try list.addOne(allocator);
result.ptr.* = element;
return result;
}
pub fn addOne(list: *List, allocator: Allocator) !Allocation {
try list.ensureCapacity(allocator, list.len + 1);
const max_allocation = list.blocks.items.len * item_count;
const result = switch (list.len < max_allocation) {
true => blk: {
const block = &list.blocks.items[list.first_block];
if (block.allocateIndex()) |element_index| {
const ptr = &block.items[element_index];
break :blk Allocation{
.ptr = ptr,
.index = .{
.element = element_index,
.block = @intCast(list.first_block),
},
};
} else |_| {
@panic("TODO");
}
},
false => blk: {
const block_index = list.blocks.items.len;
const new_block = list.blocks.addOneAssumeCapacity();
new_block.* = .{};
const element_index = new_block.allocateIndex() catch unreachable;
const ptr = &new_block.items[element_index];
list.first_block += @intFromBool(block_index != 0);
break :blk Allocation{
.ptr = ptr,
.index = .{
.element = element_index,
.block = @intCast(block_index),
},
};
},
};
list.len += 1;
return result;
}
pub fn ensureCapacity(list: *List, allocator: Allocator, new_capacity: usize) !void {
const max_allocation = list.blocks.items.len * item_count;
if (max_allocation < new_capacity) {
const block_count = new_capacity / item_count + @intFromBool(new_capacity % item_count != 0);
try list.blocks.ensureTotalCapacity(allocator, block_count);
}
}
pub fn indexOf(list: *List, elem: *const T) Index {
const address = @intFromPtr(elem);
for (list.blocks.items, 0..) |*block, block_index| {
const base = @intFromPtr(&block.items[0]);
const top = base + @sizeOf(T) * item_count;
if (address >= base and address < top) {
return .{
.block = @intCast(block_index),
.element = @intCast(@divExact(address - base, @sizeOf(T))),
};
}
}
@panic("not found");
}
test "Bitset index allocation" {
const expect = std.testing.expect;
var block = Block{};
for (0..item_count) |expected_index| {
const new_index = try block.allocateIndex();
try expect(new_index == expected_index);
}
_ = block.allocateIndex() catch return;
return error.TestUnexpectedResult;
}
};
}
pub fn enumFromString(comptime E: type, string: []const u8) ?E {
return inline for (@typeInfo(E).Enum.fields) |enum_field| {
if (std.mem.eql(u8, string, enum_field.name)) {
break @field(E, enum_field.name);
}
} else null;
}
pub fn StringKeyMap(comptime Value: type) type {
return struct {
list: std.MultiArrayList(Data) = .{},
const Key = u32;
const Data = struct {
key: Key,
value: Value,
};
pub fn length(string_map: *@This()) usize {
return string_map.list.len;
}
fn hash(string: []const u8) Key {
const string_key: Key = @truncate(std.hash.Wyhash.hash(0, string));
return string_key;
}
pub fn getKey(string_map: *const @This(), string: []const u8) ?Key {
return if (string_map.getKeyPtr(string)) |key_ptr| key_ptr.* else null;
}
pub fn getKeyPtr(string_map: *const @This(), string_key: Key) ?*const Key {
for (string_map.list.items(.key)) |*key_ptr| {
if (key_ptr.* == string_key) {
return key_ptr;
}
} else {
return null;
}
}
pub fn getValue(string_map: *const @This(), key: Key) ?Value {
if (string_map.getKeyPtr(key)) |key_ptr| {
const index = string_map.indexOfKey(key_ptr);
return string_map.list.items(.value)[index];
} else {
return null;
}
}
pub fn indexOfKey(string_map: *const @This(), key_ptr: *const Key) usize {
return @divExact(@intFromPtr(key_ptr) - @intFromPtr(string_map.list.items(.key).ptr), @sizeOf(Key));
}
const GOP = struct {
key: Key,
found_existing: bool,
};
pub fn getOrPut(string_map: *@This(), allocator: Allocator, string: []const u8, value: Value) !GOP {
const string_key: Key = @truncate(std.hash.Wyhash.hash(0, string));
for (string_map.list.items(.key)) |key| {
if (key == string_key) return .{
.key = string_key,
.found_existing = true,
};
} else {
try string_map.list.append(allocator, .{
.key = string_key,
.value = value,
});
return .{
.key = string_key,
.found_existing = false,
};
}
}
};
}
const page_size = std.mem.page_size;
extern fn pthread_jit_write_protect_np(enabled: bool) void;
pub fn mmap(size: usize, flags: packed struct {
executable: bool = false,
}) ![]align(page_size) u8 {
return switch (@import("builtin").os.tag) {
.windows => blk: {
const windows = std.os.windows;
break :blk @as([*]align(page_size) u8, @ptrCast(@alignCast(try windows.VirtualAlloc(null, size, windows.MEM_COMMIT | windows.MEM_RESERVE, windows.PAGE_EXECUTE_READWRITE))))[0..size];
},
.linux, .macos => |os_tag| blk: {
const jit = switch (os_tag) {
.macos => 0x800,
.linux => 0,
else => unreachable,
};
const execute_flag: switch (os_tag) {
.linux => u32,
.macos => c_int,
else => unreachable,
} = if (flags.executable) std.os.PROT.EXEC else 0;
const protection_flags: u32 = @intCast(std.os.PROT.READ | std.os.PROT.WRITE | execute_flag);
const mmap_flags = std.os.MAP.ANONYMOUS | std.os.MAP.PRIVATE | jit;
const result = try std.os.mmap(null, size, protection_flags, mmap_flags, -1, 0);
if (@import("builtin").cpu.arch == .aarch64 and @import("builtin").os.tag == .macos) {
if (flags.executable) {
pthread_jit_write_protect_np(false);
}
}
break :blk result;
},
else => @compileError("OS not supported"),
};
}