Skip to content

Commit cd99ab3

Browse files
committed
std.heap: rename GeneralPurposeAllocator to DebugAllocator
1 parent 5e9b8c3 commit cd99ab3

File tree

2 files changed

+88
-90
lines changed

2 files changed

+88
-90
lines changed

lib/std/heap.zig

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,20 @@ const Allocator = std.mem.Allocator;
99
const windows = std.os.windows;
1010

1111
pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator;
12-
pub const GeneralPurposeAllocatorConfig = @import("heap/general_purpose_allocator.zig").Config;
13-
pub const GeneralPurposeAllocator = @import("heap/general_purpose_allocator.zig").GeneralPurposeAllocator;
14-
pub const Check = @import("heap/general_purpose_allocator.zig").Check;
1512
pub const WasmAllocator = @import("heap/WasmAllocator.zig");
1613
pub const PageAllocator = @import("heap/PageAllocator.zig");
1714
pub const ThreadSafeAllocator = @import("heap/ThreadSafeAllocator.zig");
1815
pub const SbrkAllocator = @import("heap/sbrk_allocator.zig").SbrkAllocator;
1916
pub const FixedBufferAllocator = @import("heap/FixedBufferAllocator.zig");
2017

18+
pub const DebugAllocatorConfig = @import("heap/debug_allocator.zig").Config;
19+
pub const DebugAllocator = @import("heap/debug_allocator.zig").DebugAllocator;
20+
pub const Check = enum { ok, leak };
21+
/// Deprecated; to be removed after 0.15.0 is tagged.
22+
pub const GeneralPurposeAllocatorConfig = DebugAllocatorConfig;
23+
/// Deprecated; to be removed after 0.15.0 is tagged.
24+
pub const GeneralPurposeAllocator = DebugAllocator;
25+
2126
const memory_pool = @import("heap/memory_pool.zig");
2227
pub const MemoryPool = memory_pool.MemoryPool;
2328
pub const MemoryPoolAligned = memory_pool.MemoryPoolAligned;

lib/std/heap/general_purpose_allocator.zig renamed to lib/std/heap/debug_allocator.zig

Lines changed: 80 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,32 @@
1-
//! # General Purpose Allocator
1+
//! An allocator that is intended to be used in Debug mode.
22
//!
3-
//! ## Design Priorities
3+
//! ## Features
44
//!
5-
//! ### `OptimizationMode.debug` and `OptimizationMode.release_safe`:
5+
//! * Captures stack traces on allocation, free, and optionally resize.
6+
//! * Double free detection, which prints all three traces (first alloc, first
7+
//! free, second free).
8+
//! * Leak detection, with stack traces.
9+
//! * Never reuses memory addresses, making it easier for Zig to detect branch
10+
//! on undefined values in case of dangling pointers. This relies on
11+
//! the backing allocator to also not reuse addresses.
12+
//! * Uses a minimum backing allocation size to avoid operating system errors
13+
//! from having too many active memory mappings.
14+
//! * When a page of memory is no longer needed, give it back to resident
15+
//! memory as soon as possible, so that it causes page faults when used.
16+
//! * Cross platform. Operates based on a backing allocator which makes it work
17+
//! everywhere, even freestanding.
18+
//! * Compile-time configuration.
619
//!
7-
//! * Detect double free, and emit stack trace of:
8-
//! - Where it was first allocated
9-
//! - Where it was freed the first time
10-
//! - Where it was freed the second time
20+
//! These features require the allocator to be quite slow and wasteful. For
21+
//! example, when allocating a single byte, the efficiency is less than 1%;
22+
//! it requires more than 100 bytes of overhead to manage the allocation for
23+
//! one byte. The efficiency gets better with larger allocations.
1124
//!
12-
//! * Detect leaks and emit stack trace of:
13-
//! - Where it was allocated
25+
//! ## Basic Design
1426
//!
15-
//! * When a page of memory is no longer needed, give it back to resident memory
16-
//! as soon as possible, so that it causes page faults when used.
27+
//! Allocations are divided into two categories, small and large.
1728
//!
18-
//! * Do not re-use memory slots, so that memory safety is upheld. For small
19-
//! allocations, this is handled here; for larger ones it is handled in the
20-
//! backing allocator (by default `std.heap.page_allocator`).
21-
//!
22-
//! * Make pointer math errors unlikely to harm memory from
23-
//! unrelated allocations.
24-
//!
25-
//! * It's OK for these mechanisms to cost some extra overhead bytes.
26-
//!
27-
//! * It's OK for performance cost for these mechanisms.
28-
//!
29-
//! * Rogue memory writes should not harm the allocator's state.
30-
//!
31-
//! * Cross platform. Operates based on a backing allocator which makes it work
32-
//! everywhere, even freestanding.
33-
//!
34-
//! * Compile-time configuration.
35-
//!
36-
//! ### `OptimizationMode.release_fast` (note: not much work has gone into this use case yet):
37-
//!
38-
//! * Low fragmentation is primary concern
39-
//! * Performance of worst-case latency is secondary concern
40-
//! * Performance of average-case latency is next
41-
//! * Finally, having freed memory unmapped, and pointer math errors unlikely to
42-
//! harm memory from unrelated allocations are nice-to-haves.
43-
//!
44-
//! ### `OptimizationMode.release_small` (note: not much work has gone into this use case yet):
45-
//!
46-
//! * Small binary code size of the executable is the primary concern.
47-
//! * Next, defer to the `.release_fast` priority list.
48-
//!
49-
//! ## Basic Design:
50-
//!
51-
//! Small allocations are divided into buckets:
29+
//! Small allocations are divided into buckets based on `page_size`:
5230
//!
5331
//! ```
5432
//! index obj_size
@@ -64,33 +42,44 @@
6442
//! 9 512
6543
//! 10 1024
6644
//! 11 2048
45+
//! ...
6746
//! ```
6847
//!
48+
//! This goes on for `small_bucket_count` indexes.
49+
//!
50+
//! Allocations are grouped into an object size based on max(len, alignment),
51+
//! rounded up to the next power of two.
52+
//!
6953
//! The main allocator state has an array of all the "current" buckets for each
7054
//! size class. Each slot in the array can be null, meaning the bucket for that
7155
//! size class is not allocated. When the first object is allocated for a given
72-
//! size class, it allocates 1 page of memory from the OS. This page is
73-
//! divided into "slots" - one per allocated object. Along with the page of memory
74-
//! for object slots, as many pages as necessary are allocated to store the
75-
//! BucketHeader, followed by "used bits", and two stack traces for each slot
76-
//! (allocation trace and free trace).
56+
//! size class, it makes one `page_size` allocation from the backing allocator.
57+
//! This allocation is divided into "slots" - one per allocated object, leaving
58+
//! room for the allocation metadata (starting with `BucketHeader`), which is
59+
//! located at the very end of the "page".
60+
//!
61+
//! The allocation metadata includes "used bits" - 1 bit per slot representing
62+
//! whether the slot is used. Allocations always take the next available slot
63+
//! from the current bucket, setting the corresponding used bit, as well as
64+
//! incrementing `allocated_count`.
7765
//!
78-
//! The "used bits" are 1 bit per slot representing whether the slot is used.
79-
//! Allocations use the data to iterate to find a free slot. Frees assert that the
80-
//! corresponding bit is 1 and set it to 0.
66+
//! Frees recover the allocation metadata based on the address, length, and
67+
//! alignment, relying on the backing allocation's large alignment, combined
68+
//! with the fact that allocations are never moved from small to large, or vice
69+
//! versa.
8170
//!
82-
//! Buckets have prev and next pointers. When there is only one bucket for a given
83-
//! size class, both prev and next point to itself. When all slots of a bucket are
84-
//! used, a new bucket is allocated, and enters the doubly linked list. The main
85-
//! allocator state tracks the "current" bucket for each size class. Leak detection
86-
//! currently only checks the current bucket.
71+
//! When a bucket is full, a new one is allocated, containing a pointer to the
72+
//! previous one. This singly-linked list is iterated during leak detection.
8773
//!
88-
//! Resizing detects if the size class is unchanged or smaller, in which case the same
89-
//! pointer is returned unmodified. If a larger size class is required,
90-
//! `error.OutOfMemory` is returned.
74+
//! Resizing and remapping work the same on small allocations: if the size
75+
//! class would not change, then the operation succeeds, and the address is
76+
//! unchanged. Otherwise, the request is rejected.
9177
//!
92-
//! Large objects are allocated directly using the backing allocator and their metadata is stored
93-
//! in a `std.HashMap` using the backing allocator.
78+
//! Large objects are allocated directly using the backing allocator. Metadata
79+
//! is stored separately in a `std.HashMap` using the backing allocator.
80+
//!
81+
//! Resizing and remapping are forwarded directly to the backing allocator,
82+
//! except where such operations would change the category from large to small.
9483

9584
const std = @import("std");
9685
const builtin = @import("builtin");
@@ -172,10 +161,8 @@ pub const Config = struct {
172161
canary: usize = @truncate(0x9232a6ff85dff10f),
173162
};
174163

175-
pub const Check = enum { ok, leak };
176-
177164
/// Default initialization of this struct is deprecated; use `.init` instead.
178-
pub fn GeneralPurposeAllocator(comptime config: Config) type {
165+
pub fn DebugAllocator(comptime config: Config) type {
179166
return struct {
180167
backing_allocator: Allocator = std.heap.page_allocator,
181168
/// Tracks the active bucket, which is the one that has free slots in it.
@@ -491,8 +478,8 @@ pub fn GeneralPurposeAllocator(comptime config: Config) type {
491478
}
492479
}
493480

494-
/// Returns `Check.leak` if there were leaks; `Check.ok` otherwise.
495-
pub fn deinit(self: *Self) Check {
481+
/// Returns `std.heap.Check.leak` if there were leaks; `std.heap.Check.ok` otherwise.
482+
pub fn deinit(self: *Self) std.heap.Check {
496483
const leaks = if (config.safety) self.detectLeaks() else false;
497484
if (config.retain_metadata) self.freeRetainedMetadata();
498485
self.large_allocations.deinit(self.backing_allocator);
@@ -1041,7 +1028,7 @@ const TraceKind = enum {
10411028
const test_config = Config{};
10421029

10431030
test "small allocations - free in same order" {
1044-
var gpa = GeneralPurposeAllocator(test_config){};
1031+
var gpa = DebugAllocator(test_config){};
10451032
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
10461033
const allocator = gpa.allocator();
10471034

@@ -1060,7 +1047,7 @@ test "small allocations - free in same order" {
10601047
}
10611048

10621049
test "small allocations - free in reverse order" {
1063-
var gpa = GeneralPurposeAllocator(test_config){};
1050+
var gpa = DebugAllocator(test_config){};
10641051
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
10651052
const allocator = gpa.allocator();
10661053

@@ -1079,7 +1066,7 @@ test "small allocations - free in reverse order" {
10791066
}
10801067

10811068
test "large allocations" {
1082-
var gpa = GeneralPurposeAllocator(test_config){};
1069+
var gpa = DebugAllocator(test_config){};
10831070
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
10841071
const allocator = gpa.allocator();
10851072

@@ -1092,15 +1079,15 @@ test "large allocations" {
10921079
}
10931080

10941081
test "very large allocation" {
1095-
var gpa = GeneralPurposeAllocator(test_config){};
1082+
var gpa = DebugAllocator(test_config){};
10961083
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
10971084
const allocator = gpa.allocator();
10981085

10991086
try std.testing.expectError(error.OutOfMemory, allocator.alloc(u8, math.maxInt(usize)));
11001087
}
11011088

11021089
test "realloc" {
1103-
var gpa = GeneralPurposeAllocator(test_config){};
1090+
var gpa = DebugAllocator(test_config){};
11041091
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
11051092
const allocator = gpa.allocator();
11061093

@@ -1122,7 +1109,7 @@ test "realloc" {
11221109
}
11231110

11241111
test "shrink" {
1125-
var gpa: GeneralPurposeAllocator(test_config) = .{};
1112+
var gpa: DebugAllocator(test_config) = .{};
11261113
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
11271114
const allocator = gpa.allocator();
11281115

@@ -1147,7 +1134,7 @@ test "large object - grow" {
11471134
// Not expected to pass on targets that do not have memory mapping.
11481135
return error.SkipZigTest;
11491136
}
1150-
var gpa: GeneralPurposeAllocator(test_config) = .{};
1137+
var gpa: DebugAllocator(test_config) = .{};
11511138
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
11521139
const allocator = gpa.allocator();
11531140

@@ -1165,7 +1152,7 @@ test "large object - grow" {
11651152
}
11661153

11671154
test "realloc small object to large object" {
1168-
var gpa = GeneralPurposeAllocator(test_config){};
1155+
var gpa = DebugAllocator(test_config){};
11691156
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
11701157
const allocator = gpa.allocator();
11711158

@@ -1182,7 +1169,7 @@ test "realloc small object to large object" {
11821169
}
11831170

11841171
test "shrink large object to large object" {
1185-
var gpa: GeneralPurposeAllocator(test_config) = .{};
1172+
var gpa: DebugAllocator(test_config) = .{};
11861173
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
11871174
const allocator = gpa.allocator();
11881175

@@ -1209,7 +1196,7 @@ test "shrink large object to large object" {
12091196
test "shrink large object to large object with larger alignment" {
12101197
if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731
12111198

1212-
var gpa = GeneralPurposeAllocator(test_config){};
1199+
var gpa = DebugAllocator(test_config){};
12131200
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
12141201
const allocator = gpa.allocator();
12151202

@@ -1245,7 +1232,7 @@ test "shrink large object to large object with larger alignment" {
12451232
}
12461233

12471234
test "realloc large object to small object" {
1248-
var gpa = GeneralPurposeAllocator(test_config){};
1235+
var gpa = DebugAllocator(test_config){};
12491236
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
12501237
const allocator = gpa.allocator();
12511238

@@ -1260,7 +1247,7 @@ test "realloc large object to small object" {
12601247
}
12611248

12621249
test "overridable mutexes" {
1263-
var gpa = GeneralPurposeAllocator(.{ .MutexType = std.Thread.Mutex }){
1250+
var gpa = DebugAllocator(.{ .MutexType = std.Thread.Mutex }){
12641251
.backing_allocator = std.testing.allocator,
12651252
.mutex = std.Thread.Mutex{},
12661253
};
@@ -1272,7 +1259,11 @@ test "overridable mutexes" {
12721259
}
12731260

12741261
test "non-page-allocator backing allocator" {
1275-
var gpa = GeneralPurposeAllocator(.{}){ .backing_allocator = std.testing.allocator };
1262+
var gpa: DebugAllocator(.{
1263+
.backing_allocator_zeroes = false,
1264+
}) = .{
1265+
.backing_allocator = std.testing.allocator,
1266+
};
12761267
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
12771268
const allocator = gpa.allocator();
12781269

@@ -1283,7 +1274,7 @@ test "non-page-allocator backing allocator" {
12831274
test "realloc large object to larger alignment" {
12841275
if (!builtin.link_libc and builtin.os.tag == .wasi) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/22731
12851276

1286-
var gpa = GeneralPurposeAllocator(test_config){};
1277+
var gpa = DebugAllocator(test_config){};
12871278
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
12881279
const allocator = gpa.allocator();
12891280

@@ -1330,7 +1321,9 @@ test "large object rejects shrinking to small" {
13301321
}
13311322

13321323
var failing_allocator = std.testing.FailingAllocator.init(std.heap.page_allocator, .{ .fail_index = 3 });
1333-
var gpa: GeneralPurposeAllocator(.{}) = .{ .backing_allocator = failing_allocator.allocator() };
1324+
var gpa: DebugAllocator(.{}) = .{
1325+
.backing_allocator = failing_allocator.allocator(),
1326+
};
13341327
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
13351328
const allocator = gpa.allocator();
13361329

@@ -1345,7 +1338,7 @@ test "large object rejects shrinking to small" {
13451338
}
13461339

13471340
test "objects of size 1024 and 2048" {
1348-
var gpa = GeneralPurposeAllocator(test_config){};
1341+
var gpa = DebugAllocator(test_config){};
13491342
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
13501343
const allocator = gpa.allocator();
13511344

@@ -1357,7 +1350,7 @@ test "objects of size 1024 and 2048" {
13571350
}
13581351

13591352
test "setting a memory cap" {
1360-
var gpa = GeneralPurposeAllocator(.{ .enable_memory_limit = true }){};
1353+
var gpa = DebugAllocator(.{ .enable_memory_limit = true }){};
13611354
defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak");
13621355
const allocator = gpa.allocator();
13631356

@@ -1383,7 +1376,7 @@ test "setting a memory cap" {
13831376
}
13841377

13851378
test "large allocations count requested size not backing size" {
1386-
var gpa: GeneralPurposeAllocator(.{ .enable_memory_limit = true }) = .{};
1379+
var gpa: DebugAllocator(.{ .enable_memory_limit = true }) = .{};
13871380
const allocator = gpa.allocator();
13881381

13891382
var buf = try allocator.alignedAlloc(u8, 1, page_size + 1);
@@ -1395,7 +1388,7 @@ test "large allocations count requested size not backing size" {
13951388
}
13961389

13971390
test "retain metadata and never unmap" {
1398-
var gpa = std.heap.GeneralPurposeAllocator(.{
1391+
var gpa = std.heap.DebugAllocator(.{
13991392
.safety = true,
14001393
.never_unmap = true,
14011394
.retain_metadata = true,

0 commit comments

Comments
 (0)