mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-12-28 17:14:39 +00:00
318 lines
8.3 KiB
Zig
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);
|
|
}
|