hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/boot/heap.zig
blob: 38127c4158c32877043c512ca5f98acbe9e0e999 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
//! freelist allocator type shit

const std = @import("std");
const pmm = @import("pmm.zig");
const serial = @import("serial.zig");

const HEAP_PAGES: usize = 16384; // 64 mib
const HEADER: usize = 16; // keeps payloads 16 aligned
const ALIGN: usize = 16;
const MIN_BLOCK: usize = 32; // header + alignment usable scrap, note: dont split below this
const USED_BIT: usize = 1; // low header bit, 1 = allocated

var heap_start: usize = 0;
var heap_end: usize = 0;
// next fit rover optimization. essentially its resume scanning where hte last
// alloc landed instead of heap_start everytime. it was O(n) before
var rover: usize = 0;

// hot take: zig is very ugly
inline fn word(block: usize) *usize {
    return @ptrFromInt(block);
}

inline fn blkSize(block: usize) usize {
    return word(block).* & ~@as(usize, ALIGN - 1);
}

inline fn blkUsed(block: usize) bool {
    return (word(block).* & USED_BIT) != 0;
}

inline fn setBlk(block: usize, size: usize, used: bool) void {
    word(block).* = size | (if (used) USED_BIT else 0);
}

inline fn alignUp(value: usize, alignment: usize) usize {
    return (value + alignment - 1) & ~(alignment - 1);
}

pub fn init() void {
    const phys = pmm.allocContig(HEAP_PAGES) orelse @panic("heap: no contiguous frames for the heap");
    heap_start = pmm.physToVirt(phys);
    const total = HEAP_PAGES * pmm.PAGE_SIZE;
    heap_end = heap_start + total;
    setBlk(heap_start, total, false); // start as one big free block

    serial.write("heap: ");
    serial.writeDec(total / (1024 * 1024));
    serial.write(" MiB free-list at ");
    serial.writeHex(heap_start);
    serial.write("\r\n");
}

fn allocImpl(_: *anyopaque, len: usize, alignment: std.mem.Alignment, _: usize) ?[*]u8 {
    // we only guarantee 16 byte payload alignment
    // anything else can like actually go fuck itself
    if (alignment.toByteUnits() > ALIGN) return null;
    const need = alignUp(HEADER + len, ALIGN);

    if (rover < heap_start or rover >= heap_end) rover = heap_start;

    // next fit bull shit
    var block = rover;
    var limit = heap_end;
    var pass: u8 = 0;
    while (pass < 2) : (pass += 1) {
        while (block < limit) {
            if (!blkUsed(block)) {
                // lazy coalesce
                var size = blkSize(block);
                while (block + size < heap_end and !blkUsed(block + size)) size += blkSize(block + size);
                setBlk(block, size, false);

                if (size >= need) {
                    if (size >= need + MIN_BLOCK) {
                        setBlk(block, need, true);
                        setBlk(block + need, size - need, false); // the leftover tail
                        rover = block + need;
                    } else {
                        setBlk(block, size, true);
                        rover = block + size;
                    }
                    if (rover >= heap_end) rover = heap_start;
                    return @ptrFromInt(block + HEADER);
                }
            }
            block += blkSize(block);
        }
        // wrap
        block = heap_start;
        limit = rover;
    }
    return null;
}

fn freeImpl(_: *anyopaque, buf: []u8, _: std.mem.Alignment, _: usize) void {
    const block = @intFromPtr(buf.ptr) - HEADER;
    setBlk(block, blkSize(block), false);
}

fn resizeImpl(_: *anyopaque, buf: []u8, _: std.mem.Alignment, new_len: usize, _: usize) bool {
    const block = @intFromPtr(buf.ptr) - HEADER;
    return new_len <= blkSize(block) - HEADER;
}

fn remapImpl(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ra: usize) ?[*]u8 {
    if (resizeImpl(ctx, buf, alignment, new_len, ra)) return buf.ptr;
    return null;
}

const vtable = std.mem.Allocator.VTable{
    .alloc = allocImpl,
    .resize = resizeImpl,
    .remap = remapImpl,
    .free = freeImpl,
};

pub fn allocator() std.mem.Allocator {
    return .{ .ptr = undefined, .vtable = &vtable };
}