diff --git a/src/terminal/new/hash_map.zig b/src/terminal/new/hash_map.zig index 4c0addb71..a6433260b 100644 --- a/src/terminal/new/hash_map.zig +++ b/src/terminal/new/hash_map.zig @@ -41,6 +41,7 @@ const Wyhash = std.hash.Wyhash; const Offset = @import("size.zig").Offset; const OffsetBuf = @import("size.zig").OffsetBuf; +const getOffset = @import("size.zig").getOffset; pub fn AutoOffsetHashMap(comptime K: type, comptime V: type) type { return OffsetHashMap(K, V, AutoContext(K)); @@ -70,6 +71,7 @@ pub fn OffsetHashMap( /// This is the pointer-based map that we're wrapping. pub const Unmanaged = HashMapUnmanaged(K, V, Context); + pub const Layout = Unmanaged.Layout; /// This is the alignment that the base pointer must have. pub const base_align = Unmanaged.base_align; @@ -79,19 +81,21 @@ pub fn OffsetHashMap( /// Returns the total size of the backing memory required for a /// HashMap with the given capacity. The base ptr must also be /// aligned to base_align. - pub fn bufferSize(cap: Unmanaged.Size) usize { - const layout = Unmanaged.layoutForCapacity(cap); - return layout.total_size; + pub fn layout(cap: Unmanaged.Size) Layout { + return Unmanaged.layoutForCapacity(cap); } /// Initialize a new HashMap with the given capacity and backing /// memory. The backing memory must be aligned to base_align. - pub fn init(cap: Unmanaged.Size, buf: []u8) Self { - assert(@intFromPtr(buf.ptr) % base_align == 0); + pub fn init(buf: OffsetBuf, l: Layout) Self { + assert(@intFromPtr(buf.start()) % base_align == 0); - const m = Unmanaged.init(cap, buf); - const offset = @intFromPtr(m.metadata.?) - @intFromPtr(buf.ptr); - return .{ .metadata = .{ .offset = @intCast(offset) } }; + const m = Unmanaged.init(buf, l); + return .{ .metadata = getOffset( + Unmanaged.Metadata, + buf, + @ptrCast(m.metadata.?), + ) }; } /// Returns the pointer-based map from a base pointer. @@ -283,21 +287,19 @@ fn HashMapUnmanaged( /// Initialize a hash map with a given capacity and a buffer. The /// buffer must fit within the size defined by `layoutForCapacity`. - pub fn init(new_capacity: Size, buf: []u8) Self { - assert(@intFromPtr(buf.ptr) % base_align == 0); - const layout = layoutForCapacity(new_capacity); - assert(buf.len >= layout.total_size); + pub fn init(buf: OffsetBuf, layout: Layout) Self { + assert(@intFromPtr(buf.start()) % base_align == 0); // Get all our main pointers - const metadata_ptr: [*]Metadata = @ptrFromInt(@intFromPtr(buf.ptr) + @sizeOf(Header)); + const metadata_ptr: [*]Metadata = @ptrCast(buf.start() + @sizeOf(Header)); // Build our map var map: Self = .{ .metadata = metadata_ptr }; const hdr = map.header(); - hdr.capacity = new_capacity; + hdr.capacity = layout.capacity; hdr.size = 0; - if (@sizeOf([*]K) != 0) hdr.keys = .{ .offset = @intCast(layout.keys_start) }; - if (@sizeOf([*]V) != 0) hdr.values = .{ .offset = @intCast(layout.vals_start) }; + if (@sizeOf([*]K) != 0) hdr.keys = buf.member(K, layout.keys_start); + if (@sizeOf([*]V) != 0) hdr.values = buf.member(V, layout.vals_start); map.initMetadatas(); return map; @@ -853,7 +855,7 @@ fn HashMapUnmanaged( /// The actual size may be able to fit more than the given capacity /// because capacity is rounded up to the next power of two. This is /// a design requirement for this hash map implementation. - fn layoutForCapacity(new_capacity: Size) Layout { + pub fn layoutForCapacity(new_capacity: Size) Layout { assert(std.math.isPowerOfTwo(new_capacity)); // Pack our metadata, keys, and values. @@ -893,10 +895,11 @@ test "HashMap basic usage" { const alloc = testing.allocator; const cap = 16; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); const count = 5; var i: u32 = 0; @@ -927,9 +930,10 @@ test "HashMap ensureTotalCapacity" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); const initial_capacity = map.capacity(); try testing.expect(initial_capacity >= 20); @@ -946,9 +950,10 @@ test "HashMap ensureUnusedCapacity with tombstones" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: i32 = 0; while (i < 100) : (i += 1) { @@ -963,9 +968,10 @@ test "HashMap clearRetainingCapacity" { const cap = 16; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); map.clearRetainingCapacity(); @@ -993,9 +999,10 @@ test "HashMap ensureTotalCapacity with existing elements" { const cap = Map.minimal_capacity; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); try map.put(0, 0); try expectEqual(map.count(), 1); @@ -1011,9 +1018,10 @@ test "HashMap remove" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: u32 = 0; while (i < 16) : (i += 1) { @@ -1048,9 +1056,10 @@ test "HashMap reverse removes" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: u32 = 0; while (i < 16) : (i += 1) { @@ -1075,9 +1084,10 @@ test "HashMap multiple removes on same metadata" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: u32 = 0; while (i < 16) : (i += 1) { @@ -1117,9 +1127,10 @@ test "HashMap put and remove loop in random order" { const cap = 64; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var keys = std.ArrayList(u32).init(alloc); defer keys.deinit(); @@ -1154,9 +1165,10 @@ test "HashMap put" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: u32 = 0; while (i < 16) : (i += 1) { @@ -1184,13 +1196,10 @@ test "HashMap put full load" { const cap = 16; const alloc = testing.allocator; - const buf = try alloc.alignedAlloc( - u8, - Map.base_align, - Map.layoutForCapacity(cap).total_size, - ); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); for (0..cap) |i| try map.put(i, i); for (0..cap) |i| try expectEqual(map.get(i).?, i); @@ -1203,9 +1212,10 @@ test "HashMap putAssumeCapacity" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: u32 = 0; while (i < 20) : (i += 1) { @@ -1237,9 +1247,10 @@ test "HashMap repeat putAssumeCapacity/remove" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); const limit = cap; @@ -1272,9 +1283,10 @@ test "HashMap getOrPut" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: u32 = 0; while (i < 10) : (i += 1) { @@ -1300,9 +1312,10 @@ test "HashMap basic hash map usage" { const cap = 32; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); try testing.expect((try map.fetchPut(1, 11)) == null); try testing.expect((try map.fetchPut(2, 22)) == null); @@ -1350,9 +1363,10 @@ test "HashMap ensureUnusedCapacity" { const cap = 64; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); try map.ensureUnusedCapacity(32); try testing.expectError(error.OutOfMemory, map.ensureUnusedCapacity(cap + 1)); @@ -1363,9 +1377,10 @@ test "HashMap removeByPtr" { const cap = 64; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); var i: i32 = undefined; i = 0; @@ -1393,9 +1408,10 @@ test "HashMap removeByPtr 0 sized key" { const cap = 64; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); try map.put(0, 0); @@ -1416,9 +1432,10 @@ test "HashMap repeat fetchRemove" { const cap = 64; const alloc = testing.allocator; - const buf = try alloc.alloc(u8, Map.layoutForCapacity(cap).total_size); + const layout = Map.layoutForCapacity(cap); + const buf = try alloc.alignedAlloc(u8, Map.base_align, layout.total_size); defer alloc.free(buf); - var map = Map.init(cap, buf); + var map = Map.init(OffsetBuf.init(buf), layout); map.putAssumeCapacity(0, {}); map.putAssumeCapacity(1, {}); @@ -1440,13 +1457,13 @@ test "HashMap repeat fetchRemove" { test "OffsetHashMap basic usage" { const OffsetMap = AutoOffsetHashMap(u32, u32); + const cap = 16; const alloc = testing.allocator; - const cap = 16; - const buf = try alloc.alloc(u8, OffsetMap.Unmanaged.layoutForCapacity(cap).total_size); + const layout = OffsetMap.layout(cap); + const buf = try alloc.alignedAlloc(u8, OffsetMap.base_align, layout.total_size); defer alloc.free(buf); - - var offset_map = OffsetMap.init(cap, buf); + var offset_map = OffsetMap.init(OffsetBuf.init(buf), layout); var map = offset_map.map(buf.ptr); const count = 5; @@ -1475,13 +1492,13 @@ test "OffsetHashMap basic usage" { test "OffsetHashMap remake map" { const OffsetMap = AutoOffsetHashMap(u32, u32); + const cap = 16; const alloc = testing.allocator; - const cap = 16; - const buf = try alloc.alloc(u8, OffsetMap.Unmanaged.layoutForCapacity(cap).total_size); + const layout = OffsetMap.layout(cap); + const buf = try alloc.alignedAlloc(u8, OffsetMap.base_align, layout.total_size); defer alloc.free(buf); - - var offset_map = OffsetMap.init(cap, buf); + var offset_map = OffsetMap.init(OffsetBuf.init(buf), layout); { var map = offset_map.map(buf.ptr); diff --git a/src/terminal/new/size.zig b/src/terminal/new/size.zig index deaa56b6b..092dcd72d 100644 --- a/src/terminal/new/size.zig +++ b/src/terminal/new/size.zig @@ -36,16 +36,23 @@ pub fn Offset(comptime T: type) type { }; } -/// A type that is used to intitialize offset-based structures. -/// This allows for tracking the base pointer, the offset into -/// the base pointer we're starting, and the memory layout of -/// components. +/// Represents a buffer that is offset from some base pointer. +/// Offset-based structures should use this as their initialization +/// parameter so that they can know what segment of memory they own +/// while at the same time initializing their offset fields to be +/// against the true base. +/// +/// The term "true base" is used to describe the base address of +/// the allocation, which i.e. can include memory that you do NOT +/// own and is used by some other structures. All offsets are against +/// this "true base" so that to determine addresses structures don't +/// need to add up all the intermediary offsets. pub const OffsetBuf = struct { /// The true base pointer to the backing memory. This is /// "byte zero" of the allocation. This plus the offset make /// it easy to pass in the base pointer in all usage to this /// structure and the offsets are correct. - base: [*]u8 = 0, + base: [*]u8, /// Offset from base where the beginning of /this/ data /// structure is located. We use this so that we can slowly @@ -53,11 +60,45 @@ pub const OffsetBuf = struct { /// have the base pointer sent into functions be the true base. offset: usize = 0, - pub fn offsetBase(comptime T: type, self: OffsetBuf) [*]T { + /// Initialize a zero-offset buffer from a base. + pub fn init(base: anytype) OffsetBuf { + return initOffset(base, 0); + } + + /// Initialize from some base pointer and offset. + pub fn initOffset(base: anytype, offset: usize) OffsetBuf { + return .{ + .base = @ptrFromInt(intFromBase(base)), + .offset = offset, + }; + } + + /// The base address for the start of the data for the user + /// of this OffsetBuf. This is where your data structure should + /// begin; anything before this is NOT your memory. + pub fn start(self: OffsetBuf) [*]u8 { const ptr = self.base + self.offset; - assert(@intFromPtr(ptr) % @alignOf(T) == 0); return @ptrCast(ptr); } + + /// Returns an Offset calculation for some child member of + /// your struct. The offset is against the true base pointer + /// so that future callers can pass that in as the base. + pub fn member( + self: OffsetBuf, + comptime T: type, + len: usize, + ) Offset(T) { + return .{ .offset = @intCast(self.offset + len) }; + } + + /// Add an offset to the current offset. + pub fn add(self: OffsetBuf, offset: usize) OffsetBuf { + return .{ + .base = self.base, + .offset = self.offset + offset, + }; + } }; /// Get the offset for a given type from some base pointer to the @@ -74,7 +115,8 @@ pub fn getOffset( } fn intFromBase(base: anytype) usize { - return switch (@typeInfo(@TypeOf(base))) { + const T = @TypeOf(base); + return switch (@typeInfo(T)) { .Pointer => |v| switch (v.size) { .One, .Many, @@ -84,7 +126,11 @@ fn intFromBase(base: anytype) usize { .Slice => @intFromPtr(base.ptr), }, - else => @compileError("invalid base type"), + else => switch (T) { + OffsetBuf => @intFromPtr(base.base), + + else => @compileError("invalid base type"), + }, }; } diff --git a/src/terminal/new/style.zig b/src/terminal/new/style.zig index a34d6b706..e65250265 100644 --- a/src/terminal/new/style.zig +++ b/src/terminal/new/style.zig @@ -4,6 +4,7 @@ const color = @import("../color.zig"); const sgr = @import("../sgr.zig"); const size = @import("size.zig"); const Offset = size.Offset; +const OffsetBuf = size.OffsetBuf; const hash_map = @import("hash_map.zig"); const AutoOffsetHashMap = hash_map.AutoOffsetHashMap; @@ -49,6 +50,8 @@ pub const Style = struct { /// A set of styles. pub const Set = struct { + pub const base_align = @max(MetadataMap.base_align, IdMap.base_align); + /// The mapping of a style to associated metadata. This is /// the map that contains the actual style definitions /// (in the form of the key). @@ -74,42 +77,40 @@ pub const Set = struct { /// determine how much memory to allocate, and the layout must /// be used to initialize the set so that the set knows all /// the offsets for the various buffers. - pub fn layoutForCapacity(base: usize, cap: usize) Layout { - const md_start = std.mem.alignForward(usize, base, MetadataMap.base_align); - const md_end = md_start + MetadataMap.bufferSize(@intCast(cap)); + pub fn layout(cap: usize) Layout { + const md_layout = MetadataMap.layout(@intCast(cap)); + const md_start = 0; + const md_end = md_start + md_layout.total_size; + const id_layout = IdMap.layout(@intCast(cap)); const id_start = std.mem.alignForward(usize, md_end, IdMap.base_align); - const id_end = id_start + IdMap.bufferSize(@intCast(cap)); + const id_end = id_start + id_layout.total_size; - const total_size = id_end - base; + const total_size = id_end; return .{ - .cap = cap, .md_start = md_start, + .md_layout = md_layout, .id_start = id_start, + .id_layout = id_layout, .total_size = total_size, }; } pub const Layout = struct { - cap: usize, md_start: usize, + md_layout: MetadataMap.Layout, id_start: usize, + id_layout: IdMap.Layout, total_size: usize, }; - pub fn init(base: []u8, layout: Layout) Set { - assert(base.len >= layout.total_size); - - var styles = MetadataMap.init(@intCast(layout.cap), base[layout.md_start..]); - styles.metadata.offset += @intCast(layout.md_start); - - var id_map = IdMap.init(@intCast(layout.cap), base[layout.id_start..]); - id_map.metadata.offset += @intCast(layout.id_start); - + pub fn init(base: OffsetBuf, l: Layout) Set { + const styles_buf = base.add(l.md_start); + const id_buf = base.add(l.id_start); return .{ - .styles = styles, - .id_map = id_map, + .styles = MetadataMap.init(styles_buf, l.md_layout), + .id_map = IdMap.init(id_buf, l.id_layout), }; } @@ -179,13 +180,13 @@ test { test "Set basic usage" { const testing = std.testing; const alloc = testing.allocator; - const layout = Set.layoutForCapacity(0, 16); - const buf = try alloc.alloc(u8, layout.total_size); + const layout = Set.layout(16); + const buf = try alloc.alignedAlloc(u8, Set.base_align, layout.total_size); defer alloc.free(buf); const style: Style = .{ .flags = .{ .bold = true } }; - var set = Set.init(buf, layout); + var set = Set.init(OffsetBuf.init(buf), layout); // Upsert const meta = try set.upsert(buf, style);