lib/sys/src/heap.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const capabilities = @import("capabilities.zig");
  3 const memory = @import("memory.zig");
  4 
  5 pub const required_capabilities = capabilities.noLibc(&.{.memory_mapping});
  6 
  7 const malloc_alignment = 16;
  8 const header_size = 32;
  9 const magic: u64 = 0x5153_5953_4845_4150;
 10 
 11 const Header = extern struct {
 12     magic: u64,
 13     mapped_len: usize,
 14     payload_len: usize,
 15 };
 16 
 17 comptime {
 18     std.debug.assert(@sizeOf(Header) <= header_size);
 19     std.debug.assert(header_size % malloc_alignment == 0);
 20 }
 21 
 22 pub fn malloc(byte_count: usize) callconv(.c) ?*anyopaque {
 23     const payload_len = @max(byte_count, 1);
 24     const requested_len = std.math.add(usize, header_size, payload_len) catch return null;
 25     const mapping = memory.mapAnonymous(requested_len, .{ .read = true, .write = true }) catch return null;
 26 
 27     const header: *Header = @ptrCast(@alignCast(mapping.ptr));
 28     header.* = .{
 29         .magic = magic,
 30         .mapped_len = mapping.len,
 31         .payload_len = payload_len,
 32     };
 33 
 34     return @ptrCast(mapping.ptr + header_size);
 35 }
 36 
 37 pub fn calloc(count: usize, byte_count: usize) callconv(.c) ?*anyopaque {
 38     const total = std.math.mul(usize, count, byte_count) catch return null;
 39     const raw = malloc(total) orelse return null;
 40     const bytes: [*]u8 = @ptrCast(raw);
 41     @memset(bytes[0..total], 0);
 42     return raw;
 43 }
 44 
 45 pub fn realloc(ptr: ?*anyopaque, byte_count: usize) callconv(.c) ?*anyopaque {
 46     const raw = ptr orelse return malloc(byte_count);
 47     if (byte_count == 0) {
 48         free(raw);
 49         return null;
 50     }
 51 
 52     const header = headerFromRaw(raw);
 53     const old_len = header.payload_len;
 54     const result = malloc(byte_count) orelse return null;
 55     const source: [*]const u8 = @ptrCast(raw);
 56     const target: [*]u8 = @ptrCast(result);
 57     const copy_len = @min(old_len, byte_count);
 58     @memcpy(target[0..copy_len], source[0..copy_len]);
 59     free(raw);
 60     return result;
 61 }
 62 
 63 pub fn free(ptr: ?*anyopaque) callconv(.c) void {
 64     const raw = ptr orelse return;
 65     const header = headerFromRaw(raw);
 66 
 67     const mapped_len = header.mapped_len;
 68     header.magic = 0;
 69     const user: [*]u8 = @ptrCast(raw);
 70     const base = user - header_size;
 71     const mapping: [*]align(std.heap.page_size_min) u8 = @ptrCast(@alignCast(base));
 72     memory.unmap(mapping[0..mapped_len]);
 73 }
 74 
 75 pub fn freeAddress(address: usize) void {
 76     free(@ptrFromInt(address));
 77 }
 78 
 79 fn headerFromRaw(raw: *anyopaque) *Header {
 80     const user: [*]u8 = @ptrCast(raw);
 81     const base = user - header_size;
 82     const header: *Header = @ptrCast(@alignCast(base));
 83     if (header.magic != magic) @trap();
 84     return header;
 85 }
 86 
 87 test "malloc returns aligned memory and free accepts null" {
 88     const ptr = malloc(33) orelse return error.OutOfMemory;
 89     try std.testing.expect(std.mem.isAligned(@intFromPtr(ptr), malloc_alignment));
 90     free(ptr);
 91     free(null);
 92 }
 93 
 94 test "calloc zeros memory and realloc preserves prefix" {
 95     const raw = calloc(4, 2) orelse return error.OutOfMemory;
 96     const bytes: [*]u8 = @ptrCast(raw);
 97     try std.testing.expectEqualSlices(u8, &@as([8]u8, @splat(0)), bytes[0..8]);
 98     bytes[0] = 1;
 99     bytes[7] = 2;
100 
101     const grown = realloc(raw, 16) orelse return error.OutOfMemory;
102     const grown_bytes: [*]u8 = @ptrCast(grown);
103     try std.testing.expectEqual(@as(u8, 1), grown_bytes[0]);
104     try std.testing.expectEqual(@as(u8, 2), grown_bytes[7]);
105 
106     const shrunk = realloc(grown, 1) orelse return error.OutOfMemory;
107     const shrunk_bytes: [*]u8 = @ptrCast(shrunk);
108     try std.testing.expectEqual(@as(u8, 1), shrunk_bytes[0]);
109     try std.testing.expectEqual(@as(?*anyopaque, null), realloc(shrunk, 0));
110 }