authorgravatar for zackmusgrave20@gmail.comziggoon <zackmusgrave20@gmail.com> 2025-03-04 22:01:08-06:00
committergravatar for zackmusgrave20@gmail.comziggoon <zackmusgrave20@gmail.com> 2025-03-04 22:01:08-06:00
log16875b35985e3343586e7aafee4eeba90c11b753
tree6d5dd08ba6c37e6d33647fd7eb28a2807d7ce85b
parent79460d4a3eef8eb927b02a7eda8bc9999a766672

update std.heap.PageAllocator Windows implementation to remove race condition and utilize NtAllocateVirtualMemory / NtFreeVirtualMemory instead of VirtualAlloc and VirtualFree


1 files changed, 65 insertions(+), 62 deletions(-)

lib/std/heap/PageAllocator.zig+65-62
......@@ -6,9 +6,14 @@ const maxInt = std.math.maxInt;
66const assert = std.debug.assert;
77const native_os = builtin.os.tag;
88const windows = std.os.windows;
9const ntdll = windows.ntdll;
910const posix = std.posix;
1011const page_size_min = std.heap.page_size_min;
1112
13const SUCCESS = @import("../os/windows/ntstatus.zig").NTSTATUS.SUCCESS;
14const MEM_RESERVE_PLACEHOLDER = windows.MEM_RESERVE_PLACEHOLDER;
15const MEM_PRESERVE_PLACEHOLDER = windows.MEM_PRESERVE_PLACEHOLDER;
16
1217pub const vtable: Allocator.VTable = .{
1318 .alloc = alloc,
1419 .resize = resize,
......@@ -22,51 +27,62 @@ pub fn map(n: usize, alignment: mem.Alignment) ?[*]u8 {
2227 const alignment_bytes = alignment.toByteUnits();
2328
2429 if (native_os == .windows) {
25 // According to official documentation, VirtualAlloc aligns to page
26 // boundary, however, empirically it reserves pages on a 64K boundary.
27 // Since it is very likely the requested alignment will be honored,
28 // this logic first tries a call with exactly the size requested,
29 // before falling back to the loop below.
30 // https://devblogs.microsoft.com/oldnewthing/?p=42223
31 const addr = windows.VirtualAlloc(
32 null,
33 // VirtualAlloc will round the length to a multiple of page size.
34 // "If the lpAddress parameter is NULL, this value is rounded up to
35 // the next page boundary".
36 n,
37 windows.MEM_COMMIT | windows.MEM_RESERVE,
38 windows.PAGE_READWRITE,
39 ) catch return null;
40
41 if (mem.isAligned(@intFromPtr(addr), alignment_bytes))
42 return @ptrCast(addr);
43
44 // Fallback: reserve a range of memory large enough to find a
45 // sufficiently aligned address, then free the entire range and
46 // immediately allocate the desired subset. Another thread may have won
47 // the race to map the target range, in which case a retry is needed.
48 windows.VirtualFree(addr, 0, windows.MEM_RELEASE);
30 var base_addr: ?*anyopaque = null;
31 var size: windows.SIZE_T = n;
32
33 var status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_COMMIT | windows.MEM_RESERVE, windows.PAGE_READWRITE);
34
35 if (status == SUCCESS and mem.isAligned(@intFromPtr(base_addr), alignment_bytes)) {
36 return @ptrCast(base_addr);
37 }
38
39 if (status == SUCCESS) {
40 var region_size: windows.SIZE_T = 0;
41 _ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), &region_size, windows.MEM_RELEASE);
42 }
4943
5044 const overalloc_len = n + alignment_bytes - page_size;
5145 const aligned_len = mem.alignForward(usize, n, page_size);
5246
53 while (true) {
54 const reserved_addr = windows.VirtualAlloc(
55 null,
56 overalloc_len,
57 windows.MEM_RESERVE,
58 windows.PAGE_NOACCESS,
59 ) catch return null;
60 const aligned_addr = mem.alignForward(usize, @intFromPtr(reserved_addr), alignment_bytes);
61 windows.VirtualFree(reserved_addr, 0, windows.MEM_RELEASE);
62 const ptr = windows.VirtualAlloc(
63 @ptrFromInt(aligned_addr),
64 aligned_len,
65 windows.MEM_COMMIT | windows.MEM_RESERVE,
66 windows.PAGE_READWRITE,
67 ) catch continue;
68 return @ptrCast(ptr);
47 base_addr = null;
48 size = overalloc_len;
49
50 status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, windows.PAGE_NOACCESS);
51
52 if (status != SUCCESS) return null;
53
54 const placeholder_addr = @intFromPtr(base_addr);
55 const aligned_addr = mem.alignForward(usize, placeholder_addr, alignment_bytes);
56 const prefix_size = aligned_addr - placeholder_addr;
57
58 if (prefix_size > 0) {
59 var prefix_base = base_addr;
60 var prefix_size_param: windows.SIZE_T = prefix_size;
61 _ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&prefix_base), &prefix_size_param, windows.MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
6962 }
63
64 const suffix_start = aligned_addr + aligned_len;
65 const suffix_size = (placeholder_addr + overalloc_len) - suffix_start;
66 if (suffix_size > 0) {
67 var suffix_base = @as(?*anyopaque, @ptrFromInt(suffix_start));
68 var suffix_size_param: windows.SIZE_T = suffix_size;
69 _ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&suffix_base), &suffix_size_param, windows.MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER);
70 }
71
72 base_addr = @ptrFromInt(aligned_addr);
73 size = aligned_len;
74
75 status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_COMMIT | MEM_PRESERVE_PLACEHOLDER, windows.PAGE_READWRITE);
76
77 if (status == SUCCESS) {
78 return @ptrCast(base_addr);
79 }
80
81 base_addr = @as(?*anyopaque, @ptrFromInt(aligned_addr));
82 size = aligned_len;
83 _ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), &size, windows.MEM_RELEASE);
84
85 return null;
7086 }
7187
7288 const aligned_len = mem.alignForward(usize, n, page_size);
......@@ -104,26 +120,14 @@ fn alloc(context: *anyopaque, n: usize, alignment: mem.Alignment, ra: usize) ?[*
104120 return map(n, alignment);
105121}
106122
107fn resize(
108 context: *anyopaque,
109 memory: []u8,
110 alignment: mem.Alignment,
111 new_len: usize,
112 return_address: usize,
113) bool {
123fn resize(context: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, return_address: usize) bool {
114124 _ = context;
115125 _ = alignment;
116126 _ = return_address;
117127 return realloc(memory, new_len, false) != null;
118128}
119129
120fn remap(
121 context: *anyopaque,
122 memory: []u8,
123 alignment: mem.Alignment,
124 new_len: usize,
125 return_address: usize,
126) ?[*]u8 {
130fn remap(context: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, return_address: usize) ?[*]u8 {
127131 _ = context;
128132 _ = alignment;
129133 _ = return_address;
......@@ -139,7 +143,9 @@ fn free(context: *anyopaque, memory: []u8, alignment: mem.Alignment, return_addr
139143
140144pub fn unmap(memory: []align(page_size_min) u8) void {
141145 if (native_os == .windows) {
142 windows.VirtualFree(memory.ptr, 0, windows.MEM_RELEASE);
146 var base_addr: ?*anyopaque = memory.ptr;
147 var region_size: windows.SIZE_T = 0;
148 _ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), &region_size, windows.MEM_RELEASE);
143149 } else {
144150 const page_aligned_len = mem.alignForward(usize, memory.len, std.heap.pageSize());
145151 posix.munmap(memory.ptr[0..page_aligned_len]);
......@@ -157,13 +163,10 @@ pub fn realloc(uncasted_memory: []u8, new_len: usize, may_move: bool) ?[*]u8 {
157163 const old_addr_end = base_addr + memory.len;
158164 const new_addr_end = mem.alignForward(usize, base_addr + new_len, page_size);
159165 if (old_addr_end > new_addr_end) {
160 // For shrinking that is not releasing, we will only decommit
161 // the pages not needed anymore.
162 windows.VirtualFree(
163 @ptrFromInt(new_addr_end),
164 old_addr_end - new_addr_end,
165 windows.MEM_DECOMMIT,
166 );
166 var decommit_addr: ?*anyopaque = @ptrFromInt(new_addr_end);
167 var decommit_size: windows.SIZE_T = old_addr_end - new_addr_end;
168
169 _ = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&decommit_addr), 0, &decommit_size, windows.MEM_RESET, windows.PAGE_NOACCESS);
167170 }
168171 return memory.ptr;
169172 }