mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 00:36:07 +03:00
200 lines
6.8 KiB
Zig
200 lines
6.8 KiB
Zig
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
|
|
/// The maximum size of a page in bytes. We use a u16 here because any
|
|
/// smaller bit size by Zig is upgraded anyways to a u16 on mainstream
|
|
/// CPU architectures, and because 65KB is a reasonable page size. To
|
|
/// support better configurability, we derive everything from this.
|
|
pub const max_page_size = std.math.maxInt(u32);
|
|
|
|
/// The int type that can contain the maximum memory offset in bytes,
|
|
/// derived from the maximum terminal page size.
|
|
pub const OffsetInt = std.math.IntFittingRange(0, max_page_size - 1);
|
|
|
|
/// The int type that can contain the maximum number of cells in a page.
|
|
pub const CellCountInt = u16; // TODO: derive
|
|
//
|
|
/// The offset from the base address of the page to the start of some data.
|
|
/// This is typed for ease of use.
|
|
///
|
|
/// This is a packed struct so we can attach methods to an int.
|
|
pub fn Offset(comptime T: type) type {
|
|
return packed struct(OffsetInt) {
|
|
const Self = @This();
|
|
|
|
offset: OffsetInt = 0,
|
|
|
|
/// A slice of type T that stores via a base offset and len.
|
|
pub const Slice = struct {
|
|
offset: Self = .{},
|
|
len: usize = 0,
|
|
};
|
|
|
|
/// Returns a pointer to the start of the data, properly typed.
|
|
pub fn ptr(self: Self, base: anytype) [*]T {
|
|
// The offset must be properly aligned for the type since
|
|
// our return type is naturally aligned. We COULD modify this
|
|
// to return arbitrary alignment, but its not something we need.
|
|
const addr = intFromBase(base) + self.offset;
|
|
assert(addr % @alignOf(T) == 0);
|
|
return @ptrFromInt(addr);
|
|
}
|
|
};
|
|
}
|
|
|
|
/// 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,
|
|
|
|
/// Offset from base where the beginning of /this/ data
|
|
/// structure is located. We use this so that we can slowly
|
|
/// build up a chain of offset-based structures but always
|
|
/// have the base pointer sent into functions be the true base.
|
|
offset: usize = 0,
|
|
|
|
/// 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;
|
|
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,
|
|
};
|
|
}
|
|
|
|
/// Rebase the offset to have a zero offset by rebasing onto start.
|
|
/// This is similar to `add` but all of the offsets are merged into base.
|
|
pub fn rebase(self: OffsetBuf, offset: usize) OffsetBuf {
|
|
return .{
|
|
.base = self.start() + offset,
|
|
.offset = 0,
|
|
};
|
|
}
|
|
};
|
|
|
|
/// Get the offset for a given type from some base pointer to the
|
|
/// actual pointer to the type.
|
|
pub fn getOffset(
|
|
comptime T: type,
|
|
base: anytype,
|
|
ptr: *const T,
|
|
) Offset(T) {
|
|
const base_int = intFromBase(base);
|
|
const ptr_int = @intFromPtr(ptr);
|
|
const offset = ptr_int - base_int;
|
|
return .{ .offset = @intCast(offset) };
|
|
}
|
|
|
|
fn intFromBase(base: anytype) usize {
|
|
const T = @TypeOf(base);
|
|
return switch (@typeInfo(T)) {
|
|
.Pointer => |v| switch (v.size) {
|
|
.One,
|
|
.Many,
|
|
.C,
|
|
=> @intFromPtr(base),
|
|
|
|
.Slice => @intFromPtr(base.ptr),
|
|
},
|
|
|
|
else => switch (T) {
|
|
OffsetBuf => @intFromPtr(base.base),
|
|
|
|
else => @compileError("invalid base type"),
|
|
},
|
|
};
|
|
}
|
|
|
|
test "Offset" {
|
|
// This test is here so that if Offset changes, we can be very aware
|
|
// of this effect and think about the implications of it.
|
|
const testing = std.testing;
|
|
try testing.expect(OffsetInt == u32);
|
|
}
|
|
|
|
test "Offset ptr u8" {
|
|
const testing = std.testing;
|
|
const offset: Offset(u8) = .{ .offset = 42 };
|
|
const base_int: usize = @intFromPtr(&offset);
|
|
const actual = offset.ptr(&offset);
|
|
try testing.expectEqual(@as(usize, base_int + 42), @intFromPtr(actual));
|
|
}
|
|
|
|
test "Offset ptr structural" {
|
|
const Struct = struct { x: u32, y: u32 };
|
|
const testing = std.testing;
|
|
const offset: Offset(Struct) = .{ .offset = @alignOf(Struct) * 4 };
|
|
const base_int: usize = std.mem.alignForward(usize, @intFromPtr(&offset), @alignOf(Struct));
|
|
const base: [*]u8 = @ptrFromInt(base_int);
|
|
const actual = offset.ptr(base);
|
|
try testing.expectEqual(@as(usize, base_int + offset.offset), @intFromPtr(actual));
|
|
}
|
|
|
|
test "getOffset bytes" {
|
|
const testing = std.testing;
|
|
var widgets: []const u8 = "ABCD";
|
|
const offset = getOffset(u8, widgets.ptr, &widgets[2]);
|
|
try testing.expectEqual(@as(OffsetInt, 2), offset.offset);
|
|
}
|
|
|
|
test "getOffset structs" {
|
|
const testing = std.testing;
|
|
const Widget = struct { x: u32, y: u32 };
|
|
const widgets: []const Widget = &.{
|
|
.{ .x = 1, .y = 2 },
|
|
.{ .x = 3, .y = 4 },
|
|
.{ .x = 5, .y = 6 },
|
|
.{ .x = 7, .y = 8 },
|
|
.{ .x = 9, .y = 10 },
|
|
};
|
|
const offset = getOffset(Widget, widgets.ptr, &widgets[2]);
|
|
try testing.expectEqual(
|
|
@as(OffsetInt, @sizeOf(Widget) * 2),
|
|
offset.offset,
|
|
);
|
|
}
|