aboutsummaryrefslogtreecommitdiff
path: root/src/mimalloc_arena.zig
blob: 2c2f493ce8562a26d4d1d2e39ad3aed601345c7b (plain) (blame)
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
121
122
123
124
125
const mem = @import("std").mem;
const builtin = @import("std").builtin;
const std = @import("std");

const mimalloc = @import("./allocators/mimalloc.zig");
const Environment = @import("./env.zig");
const FeatureFlags = @import("./feature_flags.zig");
const Allocator = mem.Allocator;
const assert = std.debug.assert;

pub const Arena = struct {
    heap: *mimalloc.mi_heap_t = undefined,

    pub fn backingAllocator(this: Arena) Allocator {
        var arena = Arena{ .heap = this.heap.backing() };
        return arena.allocator();
    }

    pub fn allocator(this: Arena) Allocator {
        return Allocator{ .ptr = this.heap, .vtable = &c_allocator_vtable };
    }

    pub fn deinit(this: *Arena) void {
        mimalloc.mi_heap_destroy(this.heap);
    }

    pub fn reset(this: *Arena) void {
        this.deinit();
        this.* = init() catch unreachable;
    }

    pub fn init() !Arena {
        return Arena{ .heap = mimalloc.mi_heap_new() orelse return error.OutOfMemory };
    }

    pub fn gc(this: Arena, force: bool) void {
        mimalloc.mi_heap_collect(this.heap, force);
    }

    // Copied from rust
    const MI_MAX_ALIGN_SIZE = 16;
    inline fn mi_malloc_satisfies_alignment(alignment: usize, size: usize) bool {
        return (alignment == @sizeOf(*anyopaque) or
            (alignment == MI_MAX_ALIGN_SIZE and size > (MI_MAX_ALIGN_SIZE / 2)));
    }

    fn alignedAlloc(heap: *mimalloc.mi_heap_t, len: usize, alignment: usize) ?[*]u8 {
        if (comptime FeatureFlags.log_allocations) std.debug.print("Malloc: {d}\n", .{len});

        // this is the logic that posix_memalign does
        var ptr = if (mi_malloc_satisfies_alignment(alignment, len))
            mimalloc.mi_heap_malloc(heap, len)
        else
            mimalloc.mi_heap_malloc_aligned(heap, len, alignment);

        return @ptrCast([*]u8, ptr orelse null);
    }

    fn alloc(
        arena: *anyopaque,
        len: usize,
        alignment: u29,
        len_align: u29,
        return_address: usize,
    ) error{OutOfMemory}![]u8 {
        _ = return_address;
        assert(len > 0);
        assert(std.math.isPowerOfTwo(alignment));

        var ptr = alignedAlloc(@ptrCast(*mimalloc.mi_heap_t, arena), len, alignment) orelse return error.OutOfMemory;
        if (len_align == 0) {
            return ptr[0..len];
        }

        // std.mem.Allocator asserts this, we do it here so we can see the metadata
        if (comptime Environment.allow_assert) {
            const size = mem.alignBackwardAnyAlign(mimalloc.mi_usable_size(ptr), len_align);

            assert(size >= len);
            return ptr[0..size];
        } else {
            return ptr[0..mem.alignBackwardAnyAlign(mimalloc.mi_usable_size(ptr), len_align)];
        }
    }

    fn resize(
        _: *anyopaque,
        buf: []u8,
        buf_align: u29,
        new_len: usize,
        len_align: u29,
        return_address: usize,
    ) ?usize {
        _ = buf_align;
        _ = return_address;

        if (new_len <= buf.len) {
            return mem.alignAllocLen(buf.len, new_len, len_align);
        }

        const full_len = mimalloc.mi_usable_size(buf.ptr);
        if (new_len <= full_len) {
            return mem.alignAllocLen(full_len, new_len, len_align);
        }

        return null;
    }

    fn free(
        _: *anyopaque,
        buf: []u8,
        buf_align: u29,
        return_address: usize,
    ) void {
        _ = buf_align;
        _ = return_address;
        mimalloc.mi_free(buf.ptr);
    }
};

const c_allocator_vtable = Allocator.VTable{
    .alloc = Arena.alloc,
    .resize = Arena.resize,
    .free = Arena.free,
};