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 }