Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9911c7ea9b | |||
| b77568ca24 | |||
| a377cd68b3 | |||
| ed78052736 | |||
| 32f636b149 | |||
| cb84b24354 |
@@ -10,8 +10,6 @@ pub const impl = switch (builtin.cpu.arch) {
|
||||
else => @compileError("Unsupported architecture"),
|
||||
};
|
||||
|
||||
pub const vmm = impl.vmm;
|
||||
|
||||
/// Halts the CPU execution indefinitely, without ever returning.
|
||||
pub inline fn halt() noreturn {
|
||||
impl.halt();
|
||||
|
||||
@@ -3,8 +3,6 @@ const std = @import("std");
|
||||
const boot = @import("aarch64/boot.zig");
|
||||
const regs = @import("aarch64/regs.zig");
|
||||
|
||||
pub const vmm = @import("aarch64/vmm.zig");
|
||||
|
||||
export const _ = boot.aa64_bsp_lower_entry;
|
||||
|
||||
pub const Context = @import("aarch64/context.zig").Context;
|
||||
|
||||
@@ -8,7 +8,7 @@ pub const KERNEL_L1_INDEX: usize = L1.index(KERNEL_VIRTUAL_BASE);
|
||||
|
||||
pub const L1 = mem.TranslationLevel(30);
|
||||
pub const L2 = mem.TranslationLevel(21);
|
||||
pub const L3 = mem.vmm.L3;
|
||||
pub const L3 = mem.TranslationLevel(12);
|
||||
|
||||
pub const RawEntry = packed struct(u64) {
|
||||
// 0
|
||||
|
||||
@@ -5,8 +5,6 @@ const regs = @import("riscv64/regs.zig");
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub const vmm = @import("riscv64/vmm.zig");
|
||||
|
||||
export const _ = boot.rv64_bsp_lower_entry;
|
||||
|
||||
/// This CPU's HART (HARdware Thread) ID.
|
||||
|
||||
@@ -12,7 +12,7 @@ const EARLY_MAPPING_SIZE: usize = 16;
|
||||
|
||||
pub const L1 = mem.TranslationLevel(30);
|
||||
pub const L2 = mem.TranslationLevel(21);
|
||||
pub const L3 = mem.vmm.L3;
|
||||
pub const L3 = mem.TranslationLevel(12);
|
||||
|
||||
pub const RawEntry = packed struct(u64) {
|
||||
// 0: Valid
|
||||
|
||||
+91
-151
@@ -15,77 +15,58 @@ const Spinlock = sync.Spinlock;
|
||||
pub const MemoryRegion = struct {
|
||||
/// Name string, used to represent where the memory comes from.
|
||||
name: []const u8,
|
||||
/// Page frame number range of the region.
|
||||
/// Byte range of the memory region.
|
||||
range: Range(u64),
|
||||
};
|
||||
|
||||
const Bitmap = struct {
|
||||
data: []u64,
|
||||
/// Represents information about a single managed physical memory page.
|
||||
pub const Page = extern struct {
|
||||
/// Reference count of the page. Zero means the page is not allocated.
|
||||
refcount: u32 = 0,
|
||||
unused: [3]u32 = undefined,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub const empty: Self = .{ .data = &.{} };
|
||||
|
||||
fn get_bit(self: *Self, index: usize) u1 {
|
||||
const word_index = index / 64;
|
||||
const bit_index = index % 64;
|
||||
const masked = self.data[word_index] & (@as(u64, 1) << @intCast(bit_index));
|
||||
return if (masked == 0) 0 else 1;
|
||||
/// Returns `true` if the page is allocated/used.
|
||||
pub fn is_used(self: *const @This()) bool {
|
||||
return self.refcount != 0;
|
||||
}
|
||||
|
||||
fn set_bit(self: *Self, index: usize) void {
|
||||
const word_index = index / 64;
|
||||
const bit_index = index % 64;
|
||||
self.data[word_index] |= (@as(u64, 1) << @intCast(bit_index));
|
||||
fn make_available(self: *@This()) void {
|
||||
self.refcount = 0;
|
||||
}
|
||||
|
||||
fn clear_bit(self: *Self, index: usize) void {
|
||||
const word_index = index / 64;
|
||||
const bit_index = index % 64;
|
||||
self.data[word_index] &= ~(@as(u64, 1) << @intCast(bit_index));
|
||||
fn make_reserved(self: *@This()) void {
|
||||
self.refcount = std.math.maxInt(u32);
|
||||
}
|
||||
};
|
||||
|
||||
const PhysicalMemoryManager = struct {
|
||||
memory_start: u64,
|
||||
last_free: usize,
|
||||
len: usize,
|
||||
page_array: []Page,
|
||||
offset: u64 = 0,
|
||||
last_free: usize = 0,
|
||||
|
||||
/// Each bit represents a page, there can be more u64s than needed
|
||||
usage_bitmap: Bitmap,
|
||||
page_refcounters: []u32,
|
||||
|
||||
const empty: @This() = .{
|
||||
.memory_start = 0,
|
||||
.last_free = 0,
|
||||
.len = 0,
|
||||
.usage_bitmap = .empty,
|
||||
.page_refcounters = &.{},
|
||||
};
|
||||
const RECORDS_PER_PAGE: usize = vmm.PAGE_SIZE / @sizeOf(Page);
|
||||
|
||||
fn alloc_page(self: *@This()) ?mem.PhysicalAddress {
|
||||
for (self.last_free..self.len) |i| {
|
||||
if (!self.is_page_used(i)) {
|
||||
self.page_refcounters[i] += 1;
|
||||
self.set_page_used(i);
|
||||
self.last_free = (i + 1) % self.len;
|
||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
||||
for (self.last_free..self.page_array.len) |i| {
|
||||
if (self.page_array[i].refcount == 0) {
|
||||
self.page_array[i].refcount += 1;
|
||||
self.last_free = (i + 1) % self.page_array.len;
|
||||
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||
}
|
||||
}
|
||||
for (0..self.last_free) |i| {
|
||||
if (!self.is_page_used(i)) {
|
||||
self.page_refcounters[i] += 1;
|
||||
self.set_page_used(i);
|
||||
self.last_free = (i + 1) % self.len;
|
||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
||||
if (self.page_array[i].refcount == 0) {
|
||||
self.page_array[i].refcount += 1;
|
||||
self.last_free = (i + 1) % self.page_array.len;
|
||||
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn alloc_pages(self: *@This(), count: usize) ?mem.PhysicalAddress {
|
||||
if (self.last_free + count < self.len) {
|
||||
if (self.alloc_from(self.last_free, self.len, count)) |p| {
|
||||
if (self.last_free + count < self.page_array.len) {
|
||||
if (self.alloc_from(self.last_free, self.page_array.len, count)) |p| {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
@@ -94,19 +75,19 @@ const PhysicalMemoryManager = struct {
|
||||
|
||||
fn alloc_from(self: *@This(), start: usize, end: usize, count: usize) ?mem.PhysicalAddress {
|
||||
for (start..end) |i| {
|
||||
const taken = taken: {
|
||||
for (0..count) |j|
|
||||
if (self.is_page_used(i + j))
|
||||
break :taken true;
|
||||
break :taken false;
|
||||
};
|
||||
var taken = false;
|
||||
for (0..count) |j| {
|
||||
if (self.page_array[i + j].is_used()) {
|
||||
taken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!taken) {
|
||||
for (0..count) |j| {
|
||||
self.page_refcounters[i + j] = 1;
|
||||
self.set_page_used(i + j);
|
||||
self.page_array[i + j].refcount = 1;
|
||||
}
|
||||
return .{ .raw = self.memory_start + i * vmm.PAGE_SIZE };
|
||||
return .{ .raw = self.offset + i * vmm.PAGE_SIZE };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,11 +95,11 @@ const PhysicalMemoryManager = struct {
|
||||
}
|
||||
|
||||
fn valid_index(self: *@This(), page: mem.PhysicalAddress) usize {
|
||||
if (page.raw < self.memory_start) {
|
||||
if (page.raw < self.offset) {
|
||||
log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw});
|
||||
}
|
||||
const index = (page.raw - self.memory_start) / vmm.PAGE_SIZE;
|
||||
if (index >= self.len) {
|
||||
const index = (page.raw - self.offset) / vmm.PAGE_SIZE;
|
||||
if (index >= self.page_array.len) {
|
||||
log.panic("free_page: invalid page 0x{x}: outside of the allocation range", .{page.raw});
|
||||
}
|
||||
return index;
|
||||
@@ -126,75 +107,52 @@ const PhysicalMemoryManager = struct {
|
||||
|
||||
fn free_page(self: *@This(), page: mem.PhysicalAddress) void {
|
||||
const index = self.valid_index(page);
|
||||
if (!self.is_page_used(index)) {
|
||||
if (self.page_array[index].refcount == 0) {
|
||||
log.panic("free_page: double free of page 0x{x} detected", .{page.raw});
|
||||
}
|
||||
self.page_refcounters[index] -= 1;
|
||||
if (self.page_refcounters[index] == 0) {
|
||||
self.clear_page_used(index);
|
||||
self.page_array[index].refcount -= 1;
|
||||
if (self.page_array[index].refcount == 0) {
|
||||
self.last_free = index;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_page_used(self: *@This(), index: usize) bool {
|
||||
return self.usage_bitmap.get_bit(index) == 1;
|
||||
}
|
||||
|
||||
fn set_page_used(self: *@This(), index: usize) void {
|
||||
self.usage_bitmap.set_bit(index);
|
||||
}
|
||||
|
||||
fn clear_page_used(self: *@This(), index: usize) void {
|
||||
self.usage_bitmap.clear_bit(index);
|
||||
fn get_page(self: *@This(), page: mem.PhysicalAddress) *Page {
|
||||
const index = self.valid_index(page);
|
||||
return &self.page_array[index];
|
||||
}
|
||||
};
|
||||
|
||||
var g_memory_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var g_reserved_regions: std.BoundedArray(MemoryRegion, 16) = .{};
|
||||
var g_physical_memory_lock = Spinlock{};
|
||||
var g_physical_memory = PhysicalMemoryManager.empty;
|
||||
var g_physical_memory = PhysicalMemoryManager{ .page_array = undefined };
|
||||
|
||||
/// Adds an available memory region to the list.
|
||||
///
|
||||
/// `base` and `size` are in bytes. Regions are page-aligned "inwards", meaning the function will
|
||||
/// only add the range of full pages of the specified region. If a combination is provided that
|
||||
/// does not yield any full 4KiB pages (e.g. `base=0x1234, size=0x123`), it is ignored.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Only meaningful to call before calling `init()`.
|
||||
pub fn add_memory_region(name: []const u8, base: u64, size: u64) void {
|
||||
log.info("Memory: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||
const start = vmm.L3.align_up(base) / vmm.L3.SIZE;
|
||||
const len = vmm.L3.align_down(base + size) / vmm.L3.SIZE - start;
|
||||
if (len > 0) {
|
||||
g_memory_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) //
|
||||
catch @panic("memory regions overflow");
|
||||
}
|
||||
g_memory_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) //
|
||||
catch @panic("memory regions overflow");
|
||||
}
|
||||
|
||||
/// Adds an reserved memory region to the list.
|
||||
///
|
||||
/// `base` and `size` are in bytes. Regions are page-aligned "outwards", meaning that the
|
||||
/// reservation extends to any pages affected by the specified region.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Only meaningful to call before calling `init()`.
|
||||
pub fn add_reserved_region(name: []const u8, base: u64, size: u64) void {
|
||||
log.info("Reserved: '{s}', base 0x{x}, size 0x{x}", .{ name, base, size });
|
||||
const start = base / vmm.L3.SIZE;
|
||||
const len = vmm.L3.align_up(base + size) / vmm.L3.SIZE - start;
|
||||
if (len > 0) {
|
||||
g_reserved_regions.append(.{ .name = name, .range = .{ .start = start, .len = len } }) //
|
||||
catch @panic("reserved regions overflow");
|
||||
}
|
||||
g_reserved_regions.append(.{ .name = name, .range = .{ .start = base, .len = size } }) //
|
||||
catch @panic("reserved regions overflow");
|
||||
}
|
||||
|
||||
fn is_reserved_in(page_index: u64) ?*const MemoryRegion {
|
||||
fn is_reserved_in(page: u64) ?*const MemoryRegion {
|
||||
for (0..g_reserved_regions.len) |i| {
|
||||
const region = &g_reserved_regions.buffer[i];
|
||||
if (page_index >= region.range.start and page_index < region.range.end()) {
|
||||
if (page >= region.range.start and page < region.range.end()) {
|
||||
return region;
|
||||
}
|
||||
}
|
||||
@@ -206,7 +164,7 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count
|
||||
while (offset < region.range.len) {
|
||||
var taken: ?*const MemoryRegion = null;
|
||||
for (0..page_count) |i| {
|
||||
if (is_reserved_in(region.range.start + offset + i)) |resv| {
|
||||
if (is_reserved_in(region.range.start + offset + i * vmm.PAGE_SIZE)) |resv| {
|
||||
taken = resv;
|
||||
break;
|
||||
}
|
||||
@@ -217,51 +175,27 @@ fn alloc_from_region(region: *const MemoryRegion, reason: []const u8, page_count
|
||||
continue;
|
||||
}
|
||||
|
||||
const base = (region.range.start + offset) * vmm.L3.SIZE;
|
||||
const base = region.range.start + offset;
|
||||
add_reserved_region(reason, base, page_count * vmm.PAGE_SIZE);
|
||||
return base;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Allocates a slice of type `T` that spans the `page_count` pages.
|
||||
fn alloc_slice_pages(comptime T: type, reason: []const u8, page_count: usize) []T {
|
||||
fn alloc_page_array(page_count: usize) []Page {
|
||||
for (g_memory_regions.constSlice()) |region| {
|
||||
if (alloc_from_region(®ion, reason, page_count)) |physAddress| {
|
||||
if (alloc_from_region(®ion, "page-array", page_count)) |physAddress| {
|
||||
const vaddr = (mem.PhysicalAddress{ .raw = physAddress }).virtualize();
|
||||
const len = (page_count * vmm.PAGE_SIZE) / @sizeOf(T);
|
||||
const ptr: [*]T = @ptrFromInt(vaddr);
|
||||
const slice: []T = ptr[0..len];
|
||||
const len = page_count * PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||
const ptr: [*]Page = @ptrFromInt(vaddr);
|
||||
const slice: []Page = ptr[0..len];
|
||||
for (0..len) |i| {
|
||||
slice[i].refcount = std.math.maxInt(u32);
|
||||
}
|
||||
return slice;
|
||||
}
|
||||
}
|
||||
@panic("Failed to allocate a slice");
|
||||
}
|
||||
|
||||
/// Allocates a slice of type `T` that has at least `min_len` items, allocates in 4KiB pages.
|
||||
/// The items are zeroed out.
|
||||
fn alloc_slice(comptime T: type, reason: []const u8, min_len: usize) []T {
|
||||
const min_alloc_bytes = min_len * @sizeOf(T);
|
||||
// Round up to make sure we have enough space for the data
|
||||
const needed_pages = vmm.L3.page_count(min_alloc_bytes);
|
||||
const slice = alloc_slice_pages(T, reason, needed_pages);
|
||||
const slice_as_bytes = std.mem.sliceAsBytes(slice);
|
||||
@memset(slice_as_bytes, 0);
|
||||
return slice;
|
||||
}
|
||||
|
||||
/// Allocates a bitmap that has at least `bits_required` total bits.
|
||||
/// It can have more since we allocate 4KiB pages and the backing type is []u64
|
||||
fn alloc_bitmap(bits_required: usize) Bitmap {
|
||||
// Round up to the upper u64 that has at least `pages` bits
|
||||
const bitmap_entries = (bits_required + 63) / 64;
|
||||
const bitmap = alloc_slice(u64, "bitmap", bitmap_entries);
|
||||
return .{ .data = bitmap };
|
||||
}
|
||||
fn alloc_refcounters(count: usize) []u32 {
|
||||
const refcounters = alloc_slice(u32, "refcounters", count);
|
||||
@memset(refcounters, std.math.maxInt(u32));
|
||||
return refcounters;
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
/// Initializes the physical memory management.
|
||||
@@ -275,7 +209,6 @@ pub fn init() void {
|
||||
var memory_end: u64 = std.math.minInt(u64);
|
||||
|
||||
for (g_memory_regions.constSlice()) |region| {
|
||||
log.info("Region: {}..{}", .{ region.range.start, region.range.end() });
|
||||
if (region.range.start < memory_start) {
|
||||
memory_start = region.range.start;
|
||||
}
|
||||
@@ -284,43 +217,37 @@ pub fn init() void {
|
||||
}
|
||||
}
|
||||
|
||||
const memory_pages = memory_end - memory_start; // == bitmap bits required
|
||||
var bitmap = alloc_bitmap(memory_pages);
|
||||
const refcounters = alloc_refcounters(memory_pages);
|
||||
const memory_pages = (memory_end - memory_start) / vmm.PAGE_SIZE; // == bitmap bits required
|
||||
const page_array_pages = (memory_pages + PhysicalMemoryManager.RECORDS_PER_PAGE - 1) //
|
||||
/ PhysicalMemoryManager.RECORDS_PER_PAGE;
|
||||
|
||||
const page_array = alloc_page_array(page_array_pages);
|
||||
var available_pages: usize = 0;
|
||||
|
||||
for (g_memory_regions.constSlice()) |region| {
|
||||
const offset = region.range.start - memory_start;
|
||||
for (0..region.range.len) |i| {
|
||||
refcounters[offset + i] = 0;
|
||||
const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE;
|
||||
for (0..region.range.len / vmm.PAGE_SIZE) |i| {
|
||||
page_array[offset + i].make_available();
|
||||
available_pages += 1;
|
||||
}
|
||||
}
|
||||
for (g_reserved_regions.constSlice()) |region| {
|
||||
const offset = region.range.start - memory_start;
|
||||
for (0..region.range.len) |i| {
|
||||
if (offset + i >= memory_pages) {
|
||||
const offset = (region.range.start - memory_start) / vmm.PAGE_SIZE;
|
||||
for (0..region.range.len / vmm.PAGE_SIZE) |i| {
|
||||
if (offset + i >= page_array.len) {
|
||||
break;
|
||||
}
|
||||
refcounters[offset + i] = std.math.maxInt(u32);
|
||||
bitmap.set_bit(offset + i);
|
||||
|
||||
page_array[offset + i].make_reserved();
|
||||
available_pages -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
var size_fmt: [64]u8 = undefined;
|
||||
const size_fmt_str = mem.format_size(&size_fmt, available_pages * vmm.PAGE_SIZE);
|
||||
log.info(
|
||||
"Available memory: {s}, bitmap {*}, refcounts {*}",
|
||||
.{ size_fmt_str, bitmap.data, refcounters },
|
||||
);
|
||||
log.info("Available memory: {s}, page array {*}", .{ size_fmt_str, page_array });
|
||||
|
||||
g_physical_memory.len = memory_pages;
|
||||
g_physical_memory.memory_start = memory_start * vmm.L3.SIZE;
|
||||
g_physical_memory.usage_bitmap = bitmap;
|
||||
g_physical_memory.page_refcounters = refcounters;
|
||||
g_physical_memory.page_array = page_array;
|
||||
g_physical_memory.offset = memory_start;
|
||||
}
|
||||
|
||||
fn trace_allocation(count: usize, page: ?mem.PhysicalAddress) void {
|
||||
@@ -372,3 +299,16 @@ pub fn free_page(page: mem.PhysicalAddress) void {
|
||||
defer guard.release();
|
||||
g_physical_memory.free_page(page);
|
||||
}
|
||||
|
||||
/// Returns a `Page` struct representing the given `page`.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// The physical memory lock must be held.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic if the `page` does not represent a valid managed page.
|
||||
pub fn get_page(page: mem.PhysicalAddress) *Page {
|
||||
return g_physical_memory.get_page(page);
|
||||
}
|
||||
|
||||
@@ -1,426 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Range = @import("../util/range.zig").Range;
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
/// Describes a single virtual memory range.
|
||||
///
|
||||
/// Used by `VirtualMemoryAllocator` to track allocated/used regions.
|
||||
pub const VirtualMemoryRange = struct {
|
||||
range: Range(u64),
|
||||
|
||||
prev: ?*VirtualMemoryRange = null,
|
||||
next: ?*VirtualMemoryRange = null,
|
||||
};
|
||||
|
||||
/// Virtual memory allocator implementation.
|
||||
pub const VirtualMemoryAllocator = struct {
|
||||
gpa: Allocator,
|
||||
head: ?*VirtualMemoryRange = null,
|
||||
outer_range: Range(u64),
|
||||
|
||||
/// One of errors returned by the allocation logic + underlying allocator error.
|
||||
pub const Error = error{ already_exists, invalid_region, cannot_fit } || Allocator.Error;
|
||||
|
||||
/// An iterator over VM regions being freed.
|
||||
pub const FreeIterator = struct {
|
||||
range: Range(u64),
|
||||
vma: *VirtualMemoryAllocator,
|
||||
current: ?*VirtualMemoryRange,
|
||||
|
||||
fn next(self: *@This()) Error!?Range(u64) {
|
||||
while (self.current) |n| {
|
||||
if (n.range.intersect(&self.range)) |xs| {
|
||||
if (xs.start == n.range.start) {
|
||||
if (xs.end() == n.range.end()) {
|
||||
// Whole range encompassed by requested range
|
||||
// Unlink the node
|
||||
if (n.next) |nn| {
|
||||
nn.prev = n.prev;
|
||||
}
|
||||
if (n.prev) |np| {
|
||||
np.next = n.next;
|
||||
} else {
|
||||
self.vma.head = n.next;
|
||||
}
|
||||
// Free it
|
||||
self.current = n.next;
|
||||
self.vma.gpa.destroy(n);
|
||||
|
||||
return xs;
|
||||
}
|
||||
|
||||
// Remove space from the start
|
||||
n.range.start += xs.len;
|
||||
n.range.len -= xs.len;
|
||||
// Does not touch the end, so can be sure this is the last node
|
||||
self.current = null;
|
||||
return xs;
|
||||
} else if (xs.end() == n.range.end()) {
|
||||
n.range.len -= xs.len;
|
||||
// Continue, there might be a following node affected
|
||||
self.current = n.next;
|
||||
return xs;
|
||||
} else {
|
||||
// Insert a new node after the current one
|
||||
const new_node = try self.vma.gpa.create(VirtualMemoryRange);
|
||||
new_node.* = VirtualMemoryRange {
|
||||
.range = .{ .start = xs.end(), .len = n.range.end() - xs.end() },
|
||||
.prev = n,
|
||||
.next = n.next,
|
||||
};
|
||||
n.range.len = xs.start - n.range.start;
|
||||
if (n.next) |nn| {
|
||||
nn.prev = new_node;
|
||||
}
|
||||
n.next = new_node;
|
||||
// Requested region is fully encompassed by this one, so no intersections
|
||||
// will follow
|
||||
self.current = null;
|
||||
return xs;
|
||||
}
|
||||
} else {
|
||||
// No intersect
|
||||
self.current = n.next;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
/// Creates a new instance of a virtual memory allocator.
|
||||
pub fn init(gpa: Allocator, outer_range: Range(u64)) @This() {
|
||||
return .{
|
||||
.outer_range = outer_range,
|
||||
.gpa = gpa,
|
||||
};
|
||||
}
|
||||
|
||||
/// Allocates a free region of virtual memory of requested (`pfn_count`) size.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `cannot_fit` - if no free space found to fit the requested allocation.
|
||||
/// * Underlying allocator error - if allocation of a new node fails.
|
||||
pub fn allocate(self: *@This(), pfn_count: u64) Error!u64 {
|
||||
// Try to fit before first entry
|
||||
const gap_before_first = if (self.head) |n| (n.range.start - self.outer_range.start) else self.outer_range.len;
|
||||
|
||||
if (gap_before_first >= pfn_count) {
|
||||
var new_node = try self.gpa.create(VirtualMemoryRange);
|
||||
|
||||
new_node.range = .{ .start = self.outer_range.start, .len = pfn_count };
|
||||
new_node.next = self.head;
|
||||
new_node.prev = null;
|
||||
|
||||
if (self.head) |n| {
|
||||
n.prev = new_node;
|
||||
}
|
||||
|
||||
self.head = new_node;
|
||||
|
||||
return self.outer_range.start;
|
||||
}
|
||||
|
||||
// If cannot fit before first entry, find an entry to fit after
|
||||
var node = self.head;
|
||||
while (node) |n| {
|
||||
const gap =
|
||||
if (n.next) |nn|
|
||||
// Gap between this and next
|
||||
(nn.range.start - n.range.end())
|
||||
else
|
||||
// Gap between this and the end
|
||||
(self.outer_range.end() - n.range.end());
|
||||
|
||||
if (gap >= pfn_count) {
|
||||
// Insert after this
|
||||
const result = n.range.end();
|
||||
var new_node = try self.gpa.create(VirtualMemoryRange);
|
||||
new_node.prev = n;
|
||||
new_node.next = n.next;
|
||||
new_node.range = .{ .start = result, .len = pfn_count };
|
||||
if (n.next) |nn| {
|
||||
nn.prev = new_node;
|
||||
}
|
||||
n.next = new_node;
|
||||
return result;
|
||||
}
|
||||
|
||||
node = n.next;
|
||||
}
|
||||
|
||||
return error.cannot_fit;
|
||||
}
|
||||
|
||||
/// Inserts a reservation into the VM allocator.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `already_exists` - if the requested range intersects existing ranges.
|
||||
/// * Underlying allocator error - if allocation of a new node fails.
|
||||
pub fn insert(self: *@This(), region: Range(u64)) Error!void {
|
||||
// Validate that the range does not escape the outer range
|
||||
if (region.start < self.outer_range.start or region.end() > self.outer_range.end()) {
|
||||
return error.invalid_region;
|
||||
}
|
||||
|
||||
// Find the last node which is before the region supposed to be inserted
|
||||
var node = self.head;
|
||||
var insert_after: ?*VirtualMemoryRange = null;
|
||||
while (node) |n| {
|
||||
if (n.range.intersect(®ion) != null) {
|
||||
return error.already_exists;
|
||||
}
|
||||
|
||||
if (n.range.end() <= region.start) {
|
||||
insert_after = n;
|
||||
}
|
||||
|
||||
node = n.next;
|
||||
}
|
||||
|
||||
var new_node = try self.gpa.create(VirtualMemoryRange);
|
||||
|
||||
new_node.range = region;
|
||||
|
||||
if (insert_after) |ia| {
|
||||
new_node.prev = ia;
|
||||
new_node.next = ia.next;
|
||||
|
||||
if (ia.next) |ian| {
|
||||
ian.prev = new_node;
|
||||
}
|
||||
ia.next = new_node;
|
||||
} else {
|
||||
new_node.next = null;
|
||||
new_node.prev = null;
|
||||
|
||||
self.head = new_node;
|
||||
}
|
||||
}
|
||||
|
||||
/// Deallocates (shrinks/truncates) regions intersecting the requested range.
|
||||
pub fn free(self: *@This(), start_pfn: u64, pfn_count: u64) FreeIterator {
|
||||
const range = Range(u64) { .start = start_pfn, .len = pfn_count };
|
||||
return FreeIterator {
|
||||
.current = self.head,
|
||||
.vma = self,
|
||||
.range = range,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "Inserted entries in vmalloc are properly ordered" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x2000 });
|
||||
defer {
|
||||
while (vma.head) |n| {
|
||||
vma.head = n.next;
|
||||
std.testing.allocator.destroy(n);
|
||||
}
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
||||
{
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.next);
|
||||
try std.testing.expectEqual(null, n0.prev);
|
||||
}
|
||||
try vma.insert(.{ .start = 0x2000, .len = 0x200 });
|
||||
{
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.prev);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x2000, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
try std.testing.expectEqual(null, n1.next);
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1400, .len = 0x200 });
|
||||
{
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.prev);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1400, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x2000, n2.range.start);
|
||||
try std.testing.expectEqual(0x200, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
}
|
||||
|
||||
test "Overlapping insertions are denied" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
defer {
|
||||
while (vma.head) |n| {
|
||||
vma.head = n.next;
|
||||
std.testing.allocator.destroy(n);
|
||||
}
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1100, .len = 0x200 }));
|
||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1300, .len = 0x200 }));
|
||||
try std.testing.expectError(error.already_exists, vma.insert(.{ .start = 0x1100, .len = 0x400 }));
|
||||
}
|
||||
|
||||
test "Insertions outside of bounds are denied" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
// As above...
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x2200, .len = 0x200 }));
|
||||
// ... so below
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x200 }));
|
||||
// Crosses from below
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x1000 }));
|
||||
// Crosses into above
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x1200, .len = 0x1000 }));
|
||||
// Encompasses whole
|
||||
try std.testing.expectError(error.invalid_region, vma.insert(.{ .start = 0x200, .len = 0x2000 }));
|
||||
}
|
||||
|
||||
test "Allocations from vmalloc" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
defer {
|
||||
while (vma.head) |n| {
|
||||
vma.head = n.next;
|
||||
std.testing.allocator.destroy(n);
|
||||
}
|
||||
}
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x200 });
|
||||
try std.testing.expectEqual(0x1000, try vma.allocate(0x100));
|
||||
try std.testing.expectEqual(0x1400, try vma.allocate(0x400));
|
||||
try std.testing.expectEqual(0x1100, try vma.allocate(0x100));
|
||||
}
|
||||
|
||||
test "vmalloc free" {
|
||||
var vma = VirtualMemoryAllocator.init(std.testing.allocator, .{ .start = 0x1000, .len = 0x1000 });
|
||||
|
||||
try vma.insert(.{ .start = 0x1200, .len = 0x800 });
|
||||
try vma.insert(.{ .start = 0x1A00, .len = 0x400 });
|
||||
|
||||
// Remove nothing
|
||||
{
|
||||
var free_it = vma.free(0x1000, 0x200);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
}
|
||||
|
||||
// Remove a chunk in the middle of a node
|
||||
{
|
||||
var free_it = vma.free(0x1400, 0x400);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1400, r0.start);
|
||||
try std.testing.expectEqual(0x400, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1200, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
||||
try std.testing.expectEqual(0x400, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
|
||||
// Remove from the start
|
||||
{
|
||||
var free_it = vma.free(0x1200, 0x100);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1200, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1300, n0.range.start);
|
||||
try std.testing.expectEqual(0x100, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
||||
try std.testing.expectEqual(0x200, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
||||
try std.testing.expectEqual(0x400, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
|
||||
// Remove from the end
|
||||
{
|
||||
var free_it = vma.free(0x1900, 0x100);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1900, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1300, n0.range.start);
|
||||
try std.testing.expectEqual(0x100, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1800, n1.range.start);
|
||||
try std.testing.expectEqual(0x100, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
const n2 = n1.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n2.range.start);
|
||||
try std.testing.expectEqual(0x400, n2.range.len);
|
||||
try std.testing.expectEqual(n1, n2.prev);
|
||||
try std.testing.expectEqual(null, n2.next);
|
||||
}
|
||||
|
||||
// Remove single full
|
||||
{
|
||||
var free_it = vma.free(0x1000, 0x600);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1300, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1800, n0.range.start);
|
||||
try std.testing.expectEqual(0x100, n0.range.len);
|
||||
const n1 = n0.next.?;
|
||||
try std.testing.expectEqual(0x1A00, n1.range.start);
|
||||
try std.testing.expectEqual(0x400, n1.range.len);
|
||||
try std.testing.expectEqual(n0, n1.prev);
|
||||
try std.testing.expectEqual(null, n1.next);
|
||||
}
|
||||
|
||||
// Remove one full + one partial
|
||||
{
|
||||
var free_it = vma.free(0x1600, 0x600);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1800, r0.start);
|
||||
try std.testing.expectEqual(0x100, r0.len);
|
||||
const r1 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1A00, r1.start);
|
||||
try std.testing.expectEqual(0x200, r1.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
const n0 = vma.head.?;
|
||||
try std.testing.expectEqual(0x1C00, n0.range.start);
|
||||
try std.testing.expectEqual(0x200, n0.range.len);
|
||||
try std.testing.expectEqual(null, n0.next);
|
||||
}
|
||||
|
||||
// Remove whatever remains
|
||||
{
|
||||
var free_it = vma.free(0, 0x20000);
|
||||
const r0 = (try free_it.next()).?;
|
||||
try std.testing.expectEqual(0x1C00, r0.start);
|
||||
try std.testing.expectEqual(0x200, r0.len);
|
||||
try std.testing.expectEqual(null, free_it.next());
|
||||
|
||||
try std.testing.expectEqual(null, vma.head);
|
||||
}
|
||||
}
|
||||
+1
-14
@@ -1,12 +1,7 @@
|
||||
//! Platform-independent virtual memory management definitions.
|
||||
|
||||
const mem = @import("../mem.zig");
|
||||
|
||||
/// Last virtual memory translation level. Always 4KiB on all platforms.
|
||||
pub const L3 = mem.TranslationLevel(12);
|
||||
|
||||
/// Page size is 4KiB on all platforms.
|
||||
pub const PAGE_SIZE: usize = L3.SIZE;
|
||||
pub const PAGE_SIZE: usize = 0x1000;
|
||||
|
||||
/// Helper function to construct a "Translation Level" struct type from a bit shift.
|
||||
pub fn TranslationLevel(comptime shift: usize) type {
|
||||
@@ -33,13 +28,5 @@ pub fn TranslationLevel(comptime shift: usize) type {
|
||||
pub inline fn align_up(addr: usize) usize {
|
||||
return (addr + SIZE - 1) & ~(SIZE - 1);
|
||||
}
|
||||
|
||||
pub inline fn page_number(addr: usize) usize {
|
||||
return addr >> shift;
|
||||
}
|
||||
|
||||
pub inline fn page_count(size: usize) usize {
|
||||
return (size + SIZE - 1) / SIZE;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,2 @@
|
||||
pub const dtb = @import("util/dtb.zig");
|
||||
pub const range = @import("util/range.zig");
|
||||
pub const btree = @import("util/btree.zig");
|
||||
pub const rangemap = @import("util/rangemap.zig");
|
||||
|
||||
@@ -1,368 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
pub const Order = std.math.Order;
|
||||
|
||||
pub fn CompareFn(comptime N: type) type {
|
||||
return fn (*const N, *const N) Order;
|
||||
}
|
||||
|
||||
pub fn SearchFn(comptime N: type, comptime C: type) type {
|
||||
return fn (*const N, C) Order;
|
||||
}
|
||||
|
||||
pub fn BTree(comptime N: type, comptime compare_fn: CompareFn(N), comptime deinit_fn: ?fn (*N) void) type {
|
||||
return struct {
|
||||
gpa: Allocator,
|
||||
root: ?*Node = null,
|
||||
|
||||
pub const Error = error{ already_exists, does_not_exist } || Allocator.Error;
|
||||
|
||||
pub fn WalkFn(comptime C: type) type {
|
||||
return fn (*const Node, C) void;
|
||||
}
|
||||
|
||||
pub const Iterator = struct {
|
||||
current: ?*Node,
|
||||
|
||||
pub fn next(self: *Iterator) ?*const Node {
|
||||
while (self.current) |n| {
|
||||
const v = n;
|
||||
|
||||
if (n.right) |r| {
|
||||
// Emit
|
||||
self.current = Node.leftmost(r);
|
||||
} else {
|
||||
var nn = n;
|
||||
while (nn.parent) |p| {
|
||||
if (nn == p.right) {
|
||||
nn = p;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.current = nn.parent;
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Node = struct {
|
||||
key: N,
|
||||
parent: ?*Node = null,
|
||||
left: ?*Node = null,
|
||||
right: ?*Node = null,
|
||||
|
||||
fn init(a: Allocator, key: N) Error!*Node {
|
||||
const node = try a.create(Node);
|
||||
node.* = .{
|
||||
.key = key,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
fn deinit(node: ?*Node, a: Allocator) void {
|
||||
if (node) |n| {
|
||||
if (comptime deinit_fn) |f| {
|
||||
f(&n.key);
|
||||
}
|
||||
|
||||
Node.deinit(n.left, a);
|
||||
Node.deinit(n.right, a);
|
||||
|
||||
// Free node itself
|
||||
a.destroy(n);
|
||||
}
|
||||
}
|
||||
|
||||
fn insert(node: ?*Node, a: Allocator, key: N) Error!struct { *Node, *Node } {
|
||||
if (node) |n| {
|
||||
const ord = compare_fn(&n.key, &key);
|
||||
var inserted: *Node = undefined;
|
||||
switch (ord) {
|
||||
.lt => {
|
||||
const child, inserted = try Node.insert(n.right, a, key);
|
||||
child.parent = n;
|
||||
n.right = child;
|
||||
},
|
||||
.gt => {
|
||||
const child, inserted = try Node.insert(n.left, a, key);
|
||||
child.parent = n;
|
||||
n.left = child;
|
||||
},
|
||||
.eq => return error.already_exists,
|
||||
}
|
||||
return .{ n, inserted };
|
||||
} else {
|
||||
const n = try Node.init(a, key);
|
||||
return .{ n, n };
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_node(node: *Node, a: Allocator, destroy: bool) ?*Node {
|
||||
if (node.left == null) {
|
||||
// Only right/none
|
||||
const tmp = node.right;
|
||||
if (tmp) |t| {
|
||||
t.parent = node.parent;
|
||||
}
|
||||
// Destroy the node
|
||||
if (comptime deinit_fn) |f| {
|
||||
if (destroy) {
|
||||
f(&node.key);
|
||||
}
|
||||
}
|
||||
a.destroy(node);
|
||||
return tmp;
|
||||
}
|
||||
if (node.right == null) {
|
||||
// Only left/none
|
||||
const tmp = node.left;
|
||||
if (tmp) |t| {
|
||||
t.parent = node.parent;
|
||||
}
|
||||
// Destroy the node
|
||||
if (comptime deinit_fn) |f| {
|
||||
if (destroy) {
|
||||
f(&node.key);
|
||||
}
|
||||
}
|
||||
a.destroy(node);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
// Both
|
||||
var successor = node.right;
|
||||
while (successor) |succ| {
|
||||
if (succ.left) |l| {
|
||||
successor = l;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (successor) |succ| {
|
||||
node.key = succ.key;
|
||||
node.right = Node.remove(node.right, a, succ.key) catch unreachable;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
fn remove(node: ?*Node, a: Allocator, key: N) Error!?*Node {
|
||||
if (node) |n| {
|
||||
const ord = compare_fn(&n.key, &key);
|
||||
switch (ord) {
|
||||
.lt => n.right = try Node.remove(n.right, a, key),
|
||||
.gt => n.left = try Node.remove(n.left, a, key),
|
||||
.eq => return Node.remove_node(n, a, true),
|
||||
}
|
||||
return node;
|
||||
} else {
|
||||
return error.does_not_exist;
|
||||
}
|
||||
}
|
||||
|
||||
fn walk(node: ?*Node, ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
|
||||
if (node) |n| {
|
||||
Node.walk(n.left, ctx, walk_fn);
|
||||
walk_fn(n, ctx);
|
||||
Node.walk(n.right, ctx, walk_fn);
|
||||
}
|
||||
}
|
||||
|
||||
fn leftmost(node: ?*Node) ?*Node {
|
||||
var n = node;
|
||||
while (n) |nn| {
|
||||
if (nn.left == null) {
|
||||
break;
|
||||
}
|
||||
n = nn.left;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn init(a: std.mem.Allocator) @This() {
|
||||
return .{ .gpa = a };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
Node.deinit(self.root, self.gpa);
|
||||
}
|
||||
|
||||
pub fn iterator(self: *@This()) Iterator {
|
||||
return .{ .current = Node.leftmost(self.root) };
|
||||
}
|
||||
|
||||
pub fn insert(self: *@This(), key: N) Error!*Node {
|
||||
self.root, const inserted = try Node.insert(self.root, self.gpa, key);
|
||||
return inserted;
|
||||
}
|
||||
|
||||
pub fn remove(self: *@This(), key: N) Error!void {
|
||||
self.root = try Node.remove(self.root, self.gpa, key);
|
||||
}
|
||||
|
||||
pub fn remove_node(self: *@This(), node: *Node, destroy: bool) Error!void {
|
||||
if (node.parent) |p| {
|
||||
// Non-root node
|
||||
const np = Node.remove_node(node, self.gpa, destroy);
|
||||
if (np) |npp| {
|
||||
npp.parent = p;
|
||||
}
|
||||
if (node == p.right) {
|
||||
p.right = np;
|
||||
} else {
|
||||
p.left = np;
|
||||
}
|
||||
} else {
|
||||
// Root node
|
||||
const np = Node.remove_node(node, self.gpa, destroy);
|
||||
if (np) |npp| {
|
||||
npp.parent = null;
|
||||
}
|
||||
self.root = np;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup(self: *const @This(), key: N) ?*Node {
|
||||
const search_fn = struct {
|
||||
fn call(n: *const N, cx: N) Order {
|
||||
return compare_fn(n, &cx);
|
||||
}
|
||||
}.call;
|
||||
|
||||
return self.search(key, search_fn);
|
||||
}
|
||||
|
||||
pub fn search(
|
||||
self: *const @This(),
|
||||
ctx: anytype,
|
||||
search_fn: SearchFn(N, @TypeOf(ctx)),
|
||||
) ?*Node {
|
||||
var node = self.root;
|
||||
while (node) |n| {
|
||||
const ord = search_fn(&n.key, ctx);
|
||||
switch (ord) {
|
||||
.gt => node = n.left,
|
||||
.eq => return n,
|
||||
.lt => node = n.right,
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn walk(self: *@This(), ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
|
||||
Node.walk(self.root, ctx, walk_fn);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "BTree insertion/removal" {
|
||||
const int_compare_fn = struct {
|
||||
fn call(a: *const u32, b: *const u32) Order {
|
||||
if (a.* > b.*) {
|
||||
return .gt;
|
||||
} else if (a.* == b.*) {
|
||||
return .eq;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call;
|
||||
const Tree = BTree(u32, int_compare_fn, null);
|
||||
var tree = Tree.init(std.testing.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
for (50..100) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
for (1..50) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
|
||||
for (1..100) |i| {
|
||||
const k = @as(u32, @truncate(i));
|
||||
try std.testing.expectEqual(k, tree.lookup(k).?.key);
|
||||
}
|
||||
|
||||
for (1..100) |i| {
|
||||
const k = 100 - @as(u32, @truncate(i));
|
||||
if (i % 2 == 0) {
|
||||
try tree.remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
for (1..100) |i| {
|
||||
const k = @as(u32, @truncate(i));
|
||||
if (i % 2 == 0) {
|
||||
try std.testing.expectEqual(null, tree.lookup(k));
|
||||
} else {
|
||||
try std.testing.expectEqual(k, tree.lookup(k).?.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test "BTree removal by node" {
|
||||
const int_compare_fn = struct {
|
||||
fn call(a: *const u32, b: *const u32) Order {
|
||||
if (a.* > b.*) {
|
||||
return .gt;
|
||||
} else if (a.* == b.*) {
|
||||
return .eq;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call;
|
||||
const Tree = BTree(u32, int_compare_fn, null);
|
||||
var tree = Tree.init(std.testing.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
_ = try tree.insert(10);
|
||||
_ = try tree.insert(11);
|
||||
_ = try tree.insert(12);
|
||||
|
||||
{
|
||||
const n = tree.lookup(10).?;
|
||||
try tree.remove_node(n, true);
|
||||
}
|
||||
|
||||
try std.testing.expectEqual(null, tree.lookup(10));
|
||||
try std.testing.expectEqual(12, tree.lookup(12).?.key);
|
||||
try std.testing.expectEqual(11, tree.lookup(11).?.key);
|
||||
}
|
||||
|
||||
test "BTree iterator" {
|
||||
const int_compare_fn = struct {
|
||||
fn call(a: *const u32, b: *const u32) Order {
|
||||
if (a.* > b.*) {
|
||||
return .gt;
|
||||
} else if (a.* == b.*) {
|
||||
return .eq;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call;
|
||||
const Tree = BTree(u32, int_compare_fn, null);
|
||||
var tree = Tree.init(std.testing.allocator);
|
||||
defer tree.deinit();
|
||||
|
||||
for (50..100) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
for (1..50) |i| {
|
||||
_ = try tree.insert(@truncate(i));
|
||||
}
|
||||
|
||||
var it = tree.iterator();
|
||||
for (1..100) |i| {
|
||||
const n = it.next().?;
|
||||
try std.testing.expectEqual(i, n.key);
|
||||
}
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Utilities for manipulating ranges.
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// Non-inclusive range type over `T`.
|
||||
pub fn Range(comptime T: type) type {
|
||||
return struct {
|
||||
@@ -31,19 +29,5 @@ pub fn Range(comptime T: type) type {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
pub fn contains(self: *const @This(), scalar: T) bool {
|
||||
return scalar >= self.start and scalar - self.start < self.len;
|
||||
}
|
||||
|
||||
pub fn compare_disjoint(a: *const @This(), b: *const @This()) std.math.Order {
|
||||
if (a.start >= b.end()) {
|
||||
return .gt;
|
||||
} else if (b.start >= a.end()) {
|
||||
return .lt;
|
||||
} else {
|
||||
return .eq;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const btree = @import("btree.zig");
|
||||
|
||||
const Range = @import("range.zig").Range;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const BTree = btree.BTree;
|
||||
pub const Order = btree.Order;
|
||||
|
||||
pub fn RangeMap(
|
||||
comptime K: type,
|
||||
comptime V: type,
|
||||
comptime ops: struct {
|
||||
deinit_fn: ?fn(*V) void = null,
|
||||
merge_fn: ?fn (*const V, *const V) bool = null,
|
||||
},
|
||||
) type {
|
||||
return struct {
|
||||
pub const Node = struct {
|
||||
key: Range(K),
|
||||
value: V,
|
||||
|
||||
pub fn len(self: *const @This()) K {
|
||||
return self.key.len;
|
||||
}
|
||||
};
|
||||
|
||||
pub const WalkFn = fn (*const Node) void;
|
||||
|
||||
pub const Iterator = struct {
|
||||
inner: Tree.Iterator,
|
||||
|
||||
pub fn next(self: *Iterator) ?*const Node {
|
||||
if (self.inner.next()) |n| {
|
||||
return &n.key;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Tree = BTree(Node, compare_fn, deinit_node_fn);
|
||||
|
||||
pub const Error = error{
|
||||
scalar_out_of_range,
|
||||
range_out_of_bounds,
|
||||
} || Tree.Error;
|
||||
|
||||
fn compare_fn(a: *const Node, b: *const Node) Order {
|
||||
return Range(K).compare_disjoint(&a.key, &b.key);
|
||||
}
|
||||
|
||||
fn deinit_node_fn(n: *Node) void {
|
||||
if (comptime ops.deinit_fn) |f| {
|
||||
f(&n.value);
|
||||
}
|
||||
}
|
||||
|
||||
btree: Tree,
|
||||
|
||||
pub fn init(gpa: Allocator) @This() {
|
||||
return .{ .btree = Tree.init(gpa) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This()) void {
|
||||
self.btree.deinit();
|
||||
}
|
||||
|
||||
/// Returns the value at a given scalar point, along with the full range it belongs to.
|
||||
pub fn get_scalar(self: *const @This(), scalar: K) ?*Node {
|
||||
return if (self.get_scalar_node(scalar)) |n| &n.key else null;
|
||||
}
|
||||
|
||||
/// Same as `get_scalar()`, but returns the underlying BST node.
|
||||
pub fn get_scalar_node(self: *const @This(), scalar: K) ?*Tree.Node {
|
||||
return self.btree.search(scalar, struct {
|
||||
fn call(n: *const Node, cx: K) Order {
|
||||
if (n.key.contains(cx)) {
|
||||
return .eq;
|
||||
} else if (cx < n.key.start) {
|
||||
return .gt;
|
||||
} else {
|
||||
return .lt;
|
||||
}
|
||||
}
|
||||
}.call);
|
||||
}
|
||||
|
||||
/// Splits a given node at a scalar point inside its interval.
|
||||
///
|
||||
/// The part of the interval before `at` is considered a "left" half, the remaining
|
||||
/// part is considered a "right" half.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// The "right" halve's value after the split is left uninitialized and it is up to the
|
||||
/// caller to assign a proper value to it.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// * `scalar_out_of_range` if the given `at` value is not inside the node's interval.
|
||||
pub fn split_node(
|
||||
self: *@This(),
|
||||
node: *Tree.Node,
|
||||
at: K,
|
||||
) Error!?struct { *Tree.Node, *Tree.Node } {
|
||||
if (!node.key.key.contains(at)) {
|
||||
return error.scalar_out_of_range;
|
||||
}
|
||||
|
||||
const start = node.key.key.start;
|
||||
const end = node.key.key.end();
|
||||
|
||||
if (at == start or at == end - 1) {
|
||||
// Nothing to split here
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = node.key.value;
|
||||
|
||||
// Remove the node, don't drop the key
|
||||
try self.btree.remove_node(node, false);
|
||||
|
||||
const lnode = try self.btree.insert(
|
||||
.{ .key = .{ .start = start, .len = at - start }, .value = value },
|
||||
);
|
||||
const rnode = try self.btree.insert(
|
||||
.{ .key = .{ .start = at, .len = end - at }, .value = undefined },
|
||||
);
|
||||
|
||||
return .{ lnode, rnode };
|
||||
}
|
||||
|
||||
/// Maps some range to a value. Returns an error if the requested range crosses another
|
||||
/// mapped range.
|
||||
pub fn insert(self: *@This(), start: K, len: K, value: V) Error!*Tree.Node {
|
||||
try validate_range(start, len);
|
||||
|
||||
if (comptime ops.merge_fn) |merge_fn| {
|
||||
const left: ?*Tree.Node = if (start > 0) self.get_scalar_node(start - 1) else null;
|
||||
const right = self.get_scalar_node(start + len);
|
||||
|
||||
if (left) |l| {
|
||||
const l_start = l.key.key.start;
|
||||
|
||||
if (merge_fn(&l.key.value, &value)) {
|
||||
if (right) |r| {
|
||||
if (merge_fn(&r.key.value, &value)) {
|
||||
l.key.key.len += len + r.key.key.len;
|
||||
try self.btree.remove_node(r, true);
|
||||
return self.get_scalar_node(l_start).?;
|
||||
}
|
||||
}
|
||||
|
||||
l.key.key.len += len;
|
||||
return l;
|
||||
}
|
||||
}
|
||||
|
||||
if (right) |r| {
|
||||
// Only right node to potentially merge with
|
||||
if (merge_fn(&r.key.value, &value)) {
|
||||
const r_len = r.key.key.len;
|
||||
try self.btree.remove_node(r, true);
|
||||
return self.btree.insert(.{
|
||||
.key = .{ .start = start, .len = len + r_len },
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.btree.insert(.{
|
||||
.key = .{ .start = start, .len = len },
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn iterator(self: *@This()) Iterator {
|
||||
return .{ .inner = self.btree.iterator() };
|
||||
}
|
||||
|
||||
pub fn node_iterator(self: *@This()) Tree.Iterator {
|
||||
return self.btree.iterator();
|
||||
}
|
||||
|
||||
pub fn walk(self: *@This(), walk_fn: WalkFn) void {
|
||||
self.btree.walk(walk_fn, struct {
|
||||
fn call(n: *const Tree.Node, cx: WalkFn) void {
|
||||
cx(&n.key);
|
||||
}
|
||||
}.call);
|
||||
}
|
||||
|
||||
fn validate_range(start: K, end: K) Error!void {
|
||||
// Check for addition overflowing the K's bit size
|
||||
if (std.math.add(K, start, end) == error.Overflow) {
|
||||
return error.range_out_of_bounds;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "Range map insertion" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(10, 10, "Range 2");
|
||||
_ = try map.insert(0, 10, "Range 1");
|
||||
_ = try map.insert(20, 10, "Range 3");
|
||||
|
||||
try std.testing.expectError(error.already_exists, map.insert(5, 10, "Invalid range"));
|
||||
|
||||
_ = try map.insert(1000, 10, "Range 4");
|
||||
}
|
||||
|
||||
test "Range map merging insertion" {
|
||||
const Map = RangeMap(u32, bool, .{
|
||||
.merge_fn = struct {
|
||||
fn call(lhs: *const bool, rhs: *const bool) bool {
|
||||
return !lhs.* and !rhs.*;
|
||||
}
|
||||
}.call,
|
||||
});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
// Should not merge
|
||||
_ = try map.insert(10, 10, false);
|
||||
_ = try map.insert(0, 10, true);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
try std.testing.expectEqual(true, it.next().?.value);
|
||||
try std.testing.expectEqual(false, it.next().?.value);
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Merge left + inserted + right
|
||||
_ = try map.insert(30, 10, false);
|
||||
_ = try map.insert(20, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should not merge again
|
||||
_ = try map.insert(40, 10, true);
|
||||
_ = try map.insert(50, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 10 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should merge left + shouldn't merge right non-contiguous
|
||||
_ = try map.insert(71, 9, false);
|
||||
_ = try map.insert(60, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 20 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
const n4 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 71, .len = 9 }, n4.key);
|
||||
try std.testing.expectEqual(false, n4.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should merge left and right
|
||||
_ = try map.insert(70, 1, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
// Should merge right
|
||||
_ = try map.insert(110, 10, false);
|
||||
_ = try map.insert(100, 10, false);
|
||||
|
||||
{
|
||||
var it = map.iterator();
|
||||
const n0 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 0, .len = 10 }, n0.key);
|
||||
try std.testing.expectEqual(true, n0.value);
|
||||
|
||||
const n1 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 10, .len = 30 }, n1.key);
|
||||
try std.testing.expectEqual(false, n1.value);
|
||||
|
||||
const n2 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 40, .len = 10 }, n2.key);
|
||||
try std.testing.expectEqual(true, n2.value);
|
||||
|
||||
const n3 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 50, .len = 30 }, n3.key);
|
||||
try std.testing.expectEqual(false, n3.value);
|
||||
|
||||
const n4 = it.next().?;
|
||||
try std.testing.expectEqual(Range(u32) { .start = 100, .len = 20 }, n4.key);
|
||||
try std.testing.expectEqual(false, n4.value);
|
||||
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
}
|
||||
|
||||
test "Range map get scalar" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(10, 10, "Range [10..20)");
|
||||
_ = try map.insert(30, 10, "Range [30..40)");
|
||||
|
||||
{
|
||||
const n = map.get_scalar(15).?;
|
||||
try std.testing.expectEqual(10, n.key.start);
|
||||
try std.testing.expectEqual(20, n.key.end());
|
||||
try std.testing.expectEqualStrings("Range [10..20)", n.value);
|
||||
}
|
||||
{
|
||||
const n = map.get_scalar(35).?;
|
||||
try std.testing.expectEqual(30, n.key.start);
|
||||
try std.testing.expectEqual(40, n.key.end());
|
||||
try std.testing.expectEqualStrings("Range [30..40)", n.value);
|
||||
}
|
||||
{
|
||||
const n = map.get_scalar(30).?;
|
||||
try std.testing.expectEqual(30, n.key.start);
|
||||
try std.testing.expectEqual(40, n.key.end());
|
||||
try std.testing.expectEqualStrings("Range [30..40)", n.value);
|
||||
}
|
||||
try std.testing.expectEqual(null, map.get_scalar(20));
|
||||
try std.testing.expectEqual(null, map.get_scalar(21));
|
||||
try std.testing.expectEqual(null, map.get_scalar(9));
|
||||
try std.testing.expectEqual(null, map.get_scalar(100));
|
||||
try std.testing.expectEqual(null, map.get_scalar(40));
|
||||
try std.testing.expectEqual(null, map.get_scalar(41));
|
||||
}
|
||||
|
||||
test "Range map split" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(0x1000, 0x1000, "Range [0x1000..0x2000)");
|
||||
|
||||
const node = map.get_scalar_node(0x1000).?;
|
||||
const lnode, const rnode = (try map.split_node(node, 0x1200)).?;
|
||||
|
||||
lnode.key.value = "Left";
|
||||
rnode.key.value = "Right";
|
||||
|
||||
{
|
||||
const n = map.get_scalar(0x1100).?;
|
||||
try std.testing.expectEqual(0x1000, n.key.start);
|
||||
try std.testing.expectEqual(0x200, n.key.len);
|
||||
try std.testing.expectEqualStrings("Left", n.value);
|
||||
}
|
||||
{
|
||||
const n = map.get_scalar(0x1300).?;
|
||||
try std.testing.expectEqual(0x1200, n.key.start);
|
||||
try std.testing.expectEqual(0xE00, n.key.len);
|
||||
try std.testing.expectEqualStrings("Right", n.value);
|
||||
}
|
||||
}
|
||||
|
||||
test "Range map iterator" {
|
||||
const Map = RangeMap(u32, []const u8, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
_ = try map.insert(0x1000, 0x1000, "Range [0x1000..0x2000)");
|
||||
_ = try map.insert(0x2000, 0x1000, "Range [0x2000..0x3000)");
|
||||
_ = try map.insert(0x4000, 0x1000, "Range [0x4000..0x5000)");
|
||||
_ = try map.insert(0x3000, 0x1000, "Range [0x3000..0x4000)");
|
||||
|
||||
var it = map.iterator();
|
||||
try std.testing.expectEqualStrings("Range [0x1000..0x2000)", it.next().?.value);
|
||||
try std.testing.expectEqualStrings("Range [0x2000..0x3000)", it.next().?.value);
|
||||
try std.testing.expectEqualStrings("Range [0x3000..0x4000)", it.next().?.value);
|
||||
try std.testing.expectEqualStrings("Range [0x4000..0x5000)", it.next().?.value);
|
||||
try std.testing.expectEqual(null, it.next());
|
||||
}
|
||||
|
||||
test "Range map should disallow overflowing ranges" {
|
||||
const Map = RangeMap(u32, bool, .{});
|
||||
var map = Map.init(std.testing.allocator);
|
||||
defer map.deinit();
|
||||
|
||||
try std.testing.expectError(error.range_out_of_bounds, map.insert(0xF0000000, 0x20000000, false));
|
||||
}
|
||||
Reference in New Issue
Block a user