Files
ghostty/src/lib/allocator.zig
2025-09-24 09:27:17 -07:00

318 lines
8.3 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
/// Useful alias since they're required to create Zig allocators
pub const ZigVTable = std.mem.Allocator.VTable;
/// The VTable required by the C interface.
pub const VTable = extern struct {
alloc: *const fn (*anyopaque, len: usize, alignment: u8, ret_addr: usize) callconv(.c) ?[*]u8,
resize: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, new_len: usize, ret_addr: usize) callconv(.c) bool,
remap: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, new_len: usize, ret_addr: usize) callconv(.c) ?[*]u8,
free: *const fn (*anyopaque, memory: [*]u8, memory_len: usize, alignment: u8, ret_addr: usize) callconv(.c) void,
};
/// The Allocator interface for custom memory allocation strategies
/// within C libghostty APIs.
///
/// This -- purposely -- matches the Zig allocator interface. We do this
/// for two reasons: (1) Zig's allocator interface is well proven in
/// the real world to be flexible and useful, and (2) it allows us to
/// easily convert C allocators to Zig allocators and vice versa, since
/// we're written in Zig.
pub const Allocator = extern struct {
ctx: *anyopaque,
vtable: *const VTable,
/// vtable for the Zig allocator interface to map our extern
/// allocator to Zig's allocator interface.
pub const zig_vtable: ZigVTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
/// Create a C allocator from a Zig allocator. This requires that
/// the Zig allocator be pointer-stable for the lifetime of the
/// C allocator.
pub fn fromZig(zig_alloc: *const std.mem.Allocator) Allocator {
return .{
.ctx = @ptrCast(@constCast(zig_alloc)),
.vtable = &ZigAllocator.vtable,
};
}
/// Create a Zig allocator from this C allocator. This requires
/// a pointer to a Zig allocator vtable that we can populate with
/// our callbacks.
pub fn zig(self: *const Allocator) std.mem.Allocator {
return .{
.ptr = @ptrCast(@constCast(self)),
.vtable = &zig_vtable,
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: std.mem.Alignment,
ra: usize,
) ?[*]u8 {
const self: *Allocator = @ptrCast(@alignCast(ctx));
return self.vtable.alloc(
self.ctx,
len,
@intFromEnum(alignment),
ra,
);
}
fn resize(
ctx: *anyopaque,
old_mem: []u8,
alignment: std.mem.Alignment,
new_len: usize,
ra: usize,
) bool {
const self: *Allocator = @ptrCast(@alignCast(ctx));
return self.vtable.resize(
self.ctx,
old_mem.ptr,
old_mem.len,
@intFromEnum(alignment),
new_len,
ra,
);
}
fn remap(
ctx: *anyopaque,
old_mem: []u8,
alignment: std.mem.Alignment,
new_len: usize,
ra: usize,
) ?[*]u8 {
const self: *Allocator = @ptrCast(@alignCast(ctx));
return self.vtable.remap(
self.ctx,
old_mem.ptr,
old_mem.len,
@intFromEnum(alignment),
new_len,
ra,
);
}
fn free(
ctx: *anyopaque,
old_mem: []u8,
alignment: std.mem.Alignment,
ra: usize,
) void {
const self: *Allocator = @ptrCast(@alignCast(ctx));
self.vtable.free(
self.ctx,
old_mem.ptr,
old_mem.len,
@intFromEnum(alignment),
ra,
);
}
};
/// An allocator implementation that wraps a Zig allocator so that
/// it can be exposed to C.
const ZigAllocator = struct {
const vtable: VTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: u8,
ra: usize,
) callconv(.c) ?[*]u8 {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.alloc(
zig_alloc.ptr,
len,
@enumFromInt(alignment),
ra,
);
}
fn resize(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
new_len: usize,
ra: usize,
) callconv(.c) bool {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.resize(
zig_alloc.ptr,
memory[0..memory_len],
@enumFromInt(alignment),
new_len,
ra,
);
}
fn remap(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
new_len: usize,
ra: usize,
) callconv(.c) ?[*]u8 {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.remap(
zig_alloc.ptr,
memory[0..memory_len],
@enumFromInt(alignment),
new_len,
ra,
);
}
fn free(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
ra: usize,
) callconv(.c) void {
const zig_alloc: *const std.mem.Allocator = @ptrCast(@alignCast(ctx));
return zig_alloc.vtable.free(
zig_alloc.ptr,
memory[0..memory_len],
@enumFromInt(alignment),
ra,
);
}
};
/// C allocator (libc)
pub const CAllocator = struct {
comptime {
if (!builtin.link_libc) {
@compileError("C allocator is only available when linking against libc");
}
}
const vtable: VTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: u8,
ra: usize,
) callconv(.c) ?[*]u8 {
return std.heap.c_allocator.vtable.alloc(
ctx,
len,
@enumFromInt(alignment),
ra,
);
}
fn resize(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
new_len: usize,
ra: usize,
) callconv(.c) bool {
return std.heap.c_allocator.vtable.resize(
ctx,
memory[0..memory_len],
@enumFromInt(alignment),
new_len,
ra,
);
}
fn remap(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
new_len: usize,
ra: usize,
) callconv(.c) ?[*]u8 {
return std.heap.c_allocator.vtable.remap(
ctx,
memory[0..memory_len],
@enumFromInt(alignment),
new_len,
ra,
);
}
fn free(
ctx: *anyopaque,
memory: [*]u8,
memory_len: usize,
alignment: u8,
ra: usize,
) callconv(.c) void {
std.heap.c_allocator.vtable.free(
ctx,
memory[0..memory_len],
@enumFromInt(alignment),
ra,
);
}
};
pub const c_allocator: Allocator = .{
.ctx = undefined,
.vtable = &CAllocator.vtable,
};
/// Allocator that can be sent to the C API that does full
/// leak checking within Zig tests. This should only be used from
/// Zig tests.
pub const test_allocator: Allocator = b: {
if (!builtin.is_test) @compileError("test_allocator can only be used in tests");
break :b .fromZig(&testing.allocator);
};
test "c allocator" {
if (!comptime builtin.link_libc) return error.SkipZigTest;
const alloc = c_allocator.zig();
const str = try alloc.alloc(u8, 10);
defer alloc.free(str);
try testing.expectEqual(10, str.len);
}
test "fba allocator" {
var buf: [1024]u8 = undefined;
var fba: std.heap.FixedBufferAllocator = .init(&buf);
const zig_alloc = fba.allocator();
// Convert the Zig allocator to a C interface
const c_alloc: Allocator = .fromZig(&zig_alloc);
// Convert back to Zig so we can test it.
const alloc = c_alloc.zig();
const str = try alloc.alloc(u8, 10);
defer alloc.free(str);
try testing.expectEqual(10, str.len);
}