authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-24 21:24:50+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-24 21:24:50+01:00
log608b07a3d79554dc825292878dd4167dec143f23
tree1f53e92deb98f7f68ad26a1ed71a6aeee04692af
parent784e89fd4b081900baaa9052273bccefc91d01ed
parentbd80ad46479fc39e0d629b7a1ff48a96760bb7f2

Merge pull request 'fix `std.heap.PageAllocator` to not intrude on stacks + re-enable LoongArch CI' (#31271) from alexrp/zig:page-allocator-fixes into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31271 Reviewed-by: Andrew Kelley <andrew@ziglang.org>

4 files changed, 83 insertions(+), 40 deletions(-)

.forgejo/workflows/ci.yaml+20-21
......@@ -59,27 +59,26 @@ jobs:
5959 run: sh ci/aarch64-macos-release.sh
6060 timeout-minutes: 120
6161
62 # https://codeberg.org/ziglang/zig/issues/30800
63 #loongarch64-linux-debug:
64 # runs-on: [self-hosted, loongarch64-linux]
65 # steps:
66 # - name: Checkout
67 # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28
68 # with:
69 # fetch-depth: 0
70 # - name: Build and Test
71 # run: sh ci/loongarch64-linux-debug.sh
72 # timeout-minutes: 240
73 #loongarch64-linux-release:
74 # runs-on: [self-hosted, loongarch64-linux]
75 # steps:
76 # - name: Checkout
77 # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28
78 # with:
79 # fetch-depth: 0
80 # - name: Build and Test
81 # run: sh ci/loongarch64-linux-release.sh
82 # timeout-minutes: 180
62 loongarch64-linux-debug:
63 runs-on: [self-hosted, loongarch64-linux]
64 steps:
65 - name: Checkout
66 uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28
67 with:
68 fetch-depth: 0
69 - name: Build and Test
70 run: sh ci/loongarch64-linux-debug.sh
71 timeout-minutes: 240
72 loongarch64-linux-release:
73 runs-on: [self-hosted, loongarch64-linux]
74 steps:
75 - name: Checkout
76 uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28
77 with:
78 fetch-depth: 0
79 - name: Build and Test
80 run: sh ci/loongarch64-linux-release.sh
81 timeout-minutes: 180
8382
8483 powerpc64le-linux-debug:
8584 runs-on: [self-hosted, powerpc64le-linux]
lib/std/heap.zig-3
......@@ -41,9 +41,6 @@ pub const MemoryPoolExtra = memory_pool.Extra;
4141/// Deprecated; use `memory_pool.Options`.
4242pub const MemoryPoolOptions = memory_pool.Options;
4343
44/// TODO Utilize this on Windows.
45pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null;
46
4744/// comptime-known minimum page size of the target.
4845///
4946/// All pointers from `mmap` or `NtAllocateVirtualMemory` are aligned to at least
lib/std/heap/PageAllocator.zig+62-15
......@@ -19,6 +19,28 @@ pub const vtable: Allocator.VTable = .{
1919 .free = free,
2020};
2121
22/// Hhinting is disabled on operating systems that make an effort to not reuse
23/// mappings. For example, OpenBSD aggressively randomizes addresses of mappings
24/// that don't provide a hint (for security reasons, but it serves our needs
25/// too).
26const enable_hints = switch (builtin.target.os.tag) {
27 .openbsd => false,
28 else => true,
29};
30
31/// On operating systems that don't immediately map in the whole stack, we need
32/// to be careful to not hint into the pages after the stack guard gap, which
33/// the stack will expand into. The easiest way to avoid that is to hint in the
34/// same direction as stack growth.
35const stack_direction = builtin.target.stackGrowth();
36
37/// When hinting upwards, this points to the next page that we hope to allocate
38/// at; when hinting downwards, this points to the beginning of the last
39/// successful allocation.
40///
41/// TODO: Utilize this on Windows.
42var addr_hint: ?[*]align(page_size_min) u8 = null;
43
2244pub fn map(n: usize, alignment: Alignment) ?[*]u8 {
2345 const page_size = std.heap.pageSize();
2446 if (n >= maxInt(usize) - page_size) return null;
......@@ -41,7 +63,7 @@ pub fn map(n: usize, alignment: Alignment) ?[*]u8 {
4163 }
4264
4365 const overalloc_len = n + alignment_bytes - page_size;
44 const aligned_len = mem.alignForward(usize, n, page_size);
66 const page_aligned_len = mem.alignForward(usize, n, page_size);
4567
4668 base_addr = null;
4769 size = overalloc_len;
......@@ -60,7 +82,7 @@ pub fn map(n: usize, alignment: Alignment) ?[*]u8 {
6082 _ = ntdll.NtFreeVirtualMemory(current_process, @ptrCast(&prefix_base), &prefix_size_param, .{ .RELEASE = true, .PRESERVE_PLACEHOLDER = true });
6183 }
6284
63 const suffix_start = aligned_addr + aligned_len;
85 const suffix_start = aligned_addr + page_aligned_len;
6486 const suffix_size = (placeholder_addr + overalloc_len) - suffix_start;
6587 if (suffix_size > 0) {
6688 var suffix_base = @as(?*anyopaque, @ptrFromInt(suffix_start));
......@@ -69,7 +91,7 @@ pub fn map(n: usize, alignment: Alignment) ?[*]u8 {
6991 }
7092
7193 base_addr = @ptrFromInt(aligned_addr);
72 size = aligned_len;
94 size = page_aligned_len;
7395
7496 status = ntdll.NtAllocateVirtualMemory(current_process, @ptrCast(&base_addr), 0, &size, .{ .COMMIT = true }, .{ .READWRITE = true });
7597
......@@ -78,20 +100,34 @@ pub fn map(n: usize, alignment: Alignment) ?[*]u8 {
78100 }
79101
80102 base_addr = @as(?*anyopaque, @ptrFromInt(aligned_addr));
81 size = aligned_len;
103 size = page_aligned_len;
82104 _ = ntdll.NtFreeVirtualMemory(current_process, @ptrCast(&base_addr), &size, .{ .RELEASE = true });
83105
84106 return null;
85107 }
86108
87 const aligned_len = mem.alignForward(usize, n, page_size);
109 const page_aligned_len = mem.alignForward(usize, n, page_size);
88110 const max_drop_len = alignment_bytes -| page_size;
89 const overalloc_len = aligned_len + max_drop_len;
90 const maybe_unaligned_hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .unordered);
111 const overalloc_len = page_aligned_len + max_drop_len;
112
113 const maybe_unaligned_hint, const hint = blk: {
114 if (!enable_hints) break :blk .{ null, null };
115
116 const maybe_unaligned_hint = @atomicLoad(@TypeOf(addr_hint), &addr_hint, .unordered);
91117
92 // Aligning hint does not use mem.alignPointer, because it is slow.
93 // Aligning hint does not use mem.alignForward, because it asserts that there will be no overflow.
94 const hint: ?[*]align(page_size_min) u8 = @ptrFromInt(((@intFromPtr(maybe_unaligned_hint)) +% (alignment_bytes - 1)) & ~(alignment_bytes - 1));
118 // For the very first mmap, let the kernel pick a good starting address;
119 // we'll begin doing our hinting from there.
120 if (maybe_unaligned_hint == null) break :blk .{ null, null };
121
122 // Aligning hint does not use mem.alignPointer, because it is slow.
123 // Aligning hint does not use mem.alignForward, because it asserts that there will be no overflow.
124 const hint: ?[*]align(page_size_min) u8 = @ptrFromInt(switch (stack_direction) {
125 .down => ((@intFromPtr(maybe_unaligned_hint) -% page_aligned_len) & ~(alignment_bytes - 1)) -% max_drop_len,
126 .up => (@intFromPtr(maybe_unaligned_hint) +% (alignment_bytes - 1)) & ~(alignment_bytes - 1),
127 });
128
129 break :blk .{ maybe_unaligned_hint, hint };
130 };
95131
96132 const slice = posix.mmap(
97133 hint,
......@@ -101,16 +137,24 @@ pub fn map(n: usize, alignment: Alignment) ?[*]u8 {
101137 -1,
102138 0,
103139 ) catch return null;
104 const result_ptr = mem.alignPointer(slice.ptr, alignment_bytes) orelse return null;
140 const result_ptr = mem.alignPointer(slice.ptr, alignment_bytes).?;
141
105142 // Unmap the extra bytes that were only requested in order to guarantee
106143 // that the range of memory we were provided had a proper alignment in it
107144 // somewhere. The extra bytes could be at the beginning, or end, or both.
108145 const drop_len = result_ptr - slice.ptr;
109146 if (drop_len != 0) posix.munmap(slice[0..drop_len]);
110147 const remaining_len = overalloc_len - drop_len;
111 if (remaining_len > aligned_len) posix.munmap(@alignCast(result_ptr[aligned_len..remaining_len]));
112 const new_hint: [*]align(page_size_min) u8 = @alignCast(result_ptr + aligned_len);
113 _ = @cmpxchgStrong(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, maybe_unaligned_hint, new_hint, .monotonic, .monotonic);
148 if (remaining_len > page_aligned_len) posix.munmap(@alignCast(result_ptr[page_aligned_len..remaining_len]));
149
150 if (enable_hints) {
151 const new_hint: [*]align(page_size_min) u8 = @alignCast(result_ptr + switch (stack_direction) {
152 .up => page_aligned_len,
153 .down => 0,
154 });
155 _ = @cmpxchgStrong(@TypeOf(addr_hint), &addr_hint, maybe_unaligned_hint, new_hint, .monotonic, .monotonic);
156 }
157
114158 return result_ptr;
115159}
116160
......@@ -181,7 +225,10 @@ pub fn realloc(uncasted_memory: []u8, alignment: Alignment, new_len: usize, may_
181225 if (new_size_aligned == page_aligned_len)
182226 return memory.ptr;
183227
184 if (posix.MREMAP != void) {
228 // When the stack grows down, only use `mremap` if the allocation may move.
229 // Otherwise, we might grow the allocation and intrude on virtual address
230 // space which we want to keep available to the stack.
231 if (posix.MREMAP != void and (stack_direction == .up or may_move)) {
185232 // TODO: if the next_mmap_addr_hint is within the remapped range, update it
186233 const new_memory = posix.mremap(memory.ptr, page_aligned_len, new_size_aligned, .{ .MAYMOVE = may_move }, null) catch return null;
187234 return new_memory.ptr;
lib/std/heap/debug_allocator.zig+1-1
......@@ -266,7 +266,7 @@ pub fn DebugAllocator(comptime config: Config) type {
266266 canary: usize = config.canary,
267267
268268 fn fromPage(page_addr: usize, slot_count: usize) *BucketHeader {
269 const unaligned = page_addr + page_size - bucketSize(slot_count);
269 const unaligned = page_addr +% page_size -% bucketSize(slot_count);
270270 return @ptrFromInt(unaligned & ~(@as(usize, @alignOf(BucketHeader)) - 1));
271271 }
272272