const std = @import("std"); const builtin = @import("builtin"); const testing = std.testing; /// Convenience functions pub const convenience = @import("allocator/convenience.zig"); /// Useful alias since they're required to create Zig allocators pub const ZigVTable = std.mem.Allocator.VTable; /// The VTable required by the C interface. /// C: GhosttyAllocatorVtable 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, }; /// Returns an allocator to use for the given possibly-null C allocator, /// ensuring some allocator is always returned. pub fn default(c_alloc_: ?*const Allocator) std.mem.Allocator { // If we're given an allocator, use it. if (c_alloc_) |c_alloc| return c_alloc.zig(); // Tests always use the test allocator so we can detect leaks. if (comptime builtin.is_test) return testing.allocator; // If we have libc, use that. We prefer libc if we have it because // its generally fast but also lets the embedder easily override // malloc/free with custom allocators like mimalloc or something. if (comptime builtin.link_libc) return std.heap.c_allocator; // Wasm if (comptime builtin.target.cpu.arch.isWasm()) return std.heap.wasm_allocator; // No libc, use the preferred allocator for releases which is the // Zig SMP allocator. return std.heap.smp_allocator; } /// 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. /// /// C: GhosttyAllocator 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, ); } }; /// libc Allocator, requires linking libc pub const c_allocator: Allocator = .fromZig(&std.heap.c_allocator); /// 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); }