const builtin = @import("builtin"); const coral = @import("coral"); const ext = @import("./ext.zig"); const std = @import("std"); const AllocationNode = struct { trace: std.debug.ConfigurableTrace(2, 4, switch (builtin.mode) { .Debug, .ReleaseSafe => true, .ReleaseFast, .ReleaseSmall => false, }), next: ?*AllocationNode, size: usize, fn alloc(size: usize, return_address: usize) *AllocationNode { const node = @as(*AllocationNode, @ptrCast(@alignCast(ext.SDL_malloc(@sizeOf(AllocationNode) + size)))); node.* = .{ .size = size, .next = null, .trace = .{}, }; node.trace.addAddr(return_address, ""); return node; } fn dealloc(self: *AllocationNode) void { ext.SDL_free(self); } fn realloc(self: *AllocationNode, size: usize, return_address: usize) *AllocationNode { const node = @as(*AllocationNode, @ptrCast(@alignCast(ext.SDL_realloc(self, @sizeOf(AllocationNode) + size)))); node.* = .{ .size = size, .next = null, .trace = .{}, }; node.trace.addAddr(return_address, ""); return node; } fn owns_userdata(self: *AllocationNode, other_userdata: []const coral.io.Byte) bool { const self_userdata = self.userdata(); return self_userdata.ptr == other_userdata.ptr and self_userdata.len == other_userdata.len; } fn userdata(self: *AllocationNode) []coral.io.Byte { return @as([*]coral.io.Byte, @ptrFromInt(@intFromPtr(self) + @sizeOf(AllocationNode)))[0 .. self.size]; } }; const Context = struct { head: ?*AllocationNode = null, fn deallocate(self: *Context, allocation: []u8) void { switch (builtin.mode) { .Debug, .ReleaseSafe => { const panic_message = "incorrect allocation address for deallocating"; var current_node = self.head orelse @panic(panic_message); if (current_node.owns_userdata(allocation)) { self.head = current_node.next; return current_node.dealloc(); } while (true) { const next_node = current_node.next orelse @panic(panic_message); if (next_node.owns_userdata(allocation)) { current_node.next = next_node.next; return next_node.dealloc(); } current_node = next_node; } }, .ReleaseFast, .ReleaseSmall => { ext.SDL_free(allocation.ptr); }, } } fn reallocate(self: *Context, return_address: usize, existing_allocation: ?[]u8, size: usize) coral.io.AllocationError![]u8 { switch (builtin.mode) { .Debug, .ReleaseSafe => { if (existing_allocation) |allocation| { const panic_message = "incorrect allocation address for reallocating"; var current_node = self.head orelse @panic(panic_message); if (current_node.owns_userdata(allocation)) { const node = current_node.realloc(size, return_address); self.head = node; return node.userdata(); } while (true) { const next_node = current_node.next orelse @panic(panic_message); if (next_node.owns_userdata(allocation)) { const node = next_node.realloc(size, return_address); current_node.next = node; return node.userdata(); } current_node = next_node; } } else { const node = AllocationNode.alloc(size, return_address); if (self.head) |head| { node.next = head; } self.head = node; return node.userdata(); } }, .ReleaseFast, .ReleaseSmall => { if (existing_allocation) |allocation | { return @as([*]u8, ext.SDL_realloc(allocation.ptr, size) orelse { return error.OutOfMemory; })[0 .. size]; } return @as([*]u8, ext.SDL_malloc(size) orelse return error.OutOfMemory)[0 .. size]; }, } } }; var context = Context{}; pub const allocator = coral.io.Allocator.bind(Context, &context, .{ .reallocate = Context.reallocate, .deallocate = Context.deallocate, }); pub fn trace_leaks() void { switch (builtin.mode) { .Debug, .ReleaseSafe => { var current_node = context.head; while (current_node) |node| : (current_node = node.next) { std.debug.print("{d} byte leak at 0x{x} detected", .{ node.size, @intFromPtr(node) + @sizeOf(AllocationNode), }); node.trace.dump(); } }, .ReleaseFast, .ReleaseSmall => {}, } }