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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
|
const std = @import("std");
const assert = @import("../quirks.zig").inlineAssert;
const mem = std.mem;
const Allocator = std.mem.Allocator;
/// macOS virtual memory tags for use with mach_vm_map/mach_vm_allocate.
/// These identify memory regions in tools like vmmap and Instruments.
pub const VMTag = enum(u8) {
application_specific_1 = 240,
application_specific_2 = 241,
application_specific_3 = 242,
application_specific_4 = 243,
application_specific_5 = 244,
application_specific_6 = 245,
application_specific_7 = 246,
application_specific_8 = 247,
application_specific_9 = 248,
application_specific_10 = 249,
application_specific_11 = 250,
application_specific_12 = 251,
application_specific_13 = 252,
application_specific_14 = 253,
application_specific_15 = 254,
application_specific_16 = 255,
// We ignore the rest because we never realistic set them.
_,
/// Converts the tag to the format expected by mach_vm_map/mach_vm_allocate.
/// Equivalent to C macro: VM_MAKE_TAG(tag)
pub fn make(self: VMTag) i32 {
return @bitCast(@as(u32, @intFromEnum(self)) << 24);
}
};
/// Creates a page allocator that tags all allocated memory with the given
/// VMTag.
pub fn taggedPageAllocator(tag: VMTag) Allocator {
return .{
// We smuggle the tag in as the context pointer.
.ptr = @ptrFromInt(@as(usize, @intFromEnum(tag))),
.vtable = &TaggedPageAllocator.vtable,
};
}
/// This is based heavily on the Zig 0.15.2 PageAllocator implementation,
/// with only the posix implementation. Zig 0.15.2 is MIT licensed.
const TaggedPageAllocator = struct {
// Static local next_mmap_addr_hint since this has been removed from stdlib now
var next_mmap_addr_hint: ?[*]align(std.heap.page_size_min) u8 = null;
pub const vtable: Allocator.VTable = .{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
};
fn alloc(context: *anyopaque, n: usize, alignment: mem.Alignment, ra: usize) ?[*]u8 {
_ = ra;
assert(n > 0);
const tag: VMTag = @enumFromInt(@as(u8, @truncate(@intFromPtr(context))));
return map(n, alignment, tag);
}
fn resize(context: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, return_address: usize) bool {
_ = context;
_ = alignment;
_ = return_address;
return realloc(memory, new_len, false) != null;
}
fn remap(context: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, return_address: usize) ?[*]u8 {
_ = context;
_ = alignment;
_ = return_address;
return realloc(memory, new_len, true);
}
fn free(context: *anyopaque, memory: []u8, alignment: mem.Alignment, return_address: usize) void {
_ = context;
_ = alignment;
_ = return_address;
return unmap(@alignCast(memory));
}
pub fn map(n: usize, alignment: mem.Alignment, tag: VMTag) ?[*]u8 {
const page_size = std.heap.pageSize();
if (n >= std.math.maxInt(usize) - page_size) return null;
const alignment_bytes = alignment.toByteUnits();
const aligned_len = mem.alignForward(usize, n, page_size);
const max_drop_len = alignment_bytes - @min(alignment_bytes, page_size);
const overalloc_len = if (max_drop_len <= aligned_len - n)
aligned_len
else
mem.alignForward(usize, aligned_len + max_drop_len, page_size);
const hint = @atomicLoad(@TypeOf(next_mmap_addr_hint), &next_mmap_addr_hint, .unordered);
const slice = std.posix.mmap(
hint,
overalloc_len,
.{ .READ = true, .WRITE = true },
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
tag.make(),
0,
) catch return null;
const result_ptr = mem.alignPointer(slice.ptr, alignment_bytes) orelse return null;
// Unmap the extra bytes that were only requested in order to guarantee
// that the range of memory we were provided had a proper alignment in it
// somewhere. The extra bytes could be at the beginning, or end, or both.
const drop_len = result_ptr - slice.ptr;
if (drop_len != 0) std.posix.munmap(slice[0..drop_len]);
const remaining_len = overalloc_len - drop_len;
if (remaining_len > aligned_len) std.posix.munmap(@alignCast(result_ptr[aligned_len..remaining_len]));
const new_hint: [*]align(std.heap.page_size_min) u8 = @alignCast(result_ptr + aligned_len);
_ = @cmpxchgStrong(@TypeOf(next_mmap_addr_hint), &next_mmap_addr_hint, hint, new_hint, .monotonic, .monotonic);
return result_ptr;
}
pub fn unmap(memory: []align(std.heap.page_size_min) u8) void {
const page_aligned_len = mem.alignForward(usize, memory.len, std.heap.pageSize());
std.posix.munmap(memory.ptr[0..page_aligned_len]);
}
pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 {
const memory: []align(std.heap.page_size_min) u8 = @alignCast(uncasted_memory);
const page_size = std.heap.pageSize();
const new_size_aligned = mem.alignForward(usize, new_len, page_size);
const page_aligned_len = mem.alignForward(usize, memory.len, page_size);
if (new_size_aligned == page_aligned_len)
return memory.ptr;
if (std.posix.MREMAP != void) {
// TODO: if the next_mmap_addr_hint is within the remapped range, update it
const new_memory = std.posix.mremap(memory.ptr, memory.len, new_len, .{ .MAYMOVE = may_move }, null) catch return null;
return new_memory.ptr;
}
if (new_size_aligned < page_aligned_len) {
const ptr = memory.ptr + new_size_aligned;
// TODO: if the next_mmap_addr_hint is within the unmapped range, update it
std.posix.munmap(@alignCast(ptr[0 .. page_aligned_len - new_size_aligned]));
return memory.ptr;
}
return null;
}
};
test "VMTag.make" {
try std.testing.expectEqual(@as(i32, @bitCast(@as(u32, 240) << 24)), VMTag.application_specific_1.make());
}
|