diff --git a/lib/compiler/resinator/source_mapping.zig b/lib/compiler/resinator/source_mapping.zig index 928a7205c6..7e3bc88e1d 100644 --- a/lib/compiler/resinator/source_mapping.zig +++ b/lib/compiler/resinator/source_mapping.zig @@ -723,7 +723,7 @@ pub const SourceMappings = struct { /// The default assumes that the first filename added is the root file. /// The value should be set to the correct offset if that assumption does not hold. root_filename_offset: u32 = 0, - source_node_pool: std.heap.MemoryPool(Sources.Node) = std.heap.MemoryPool(Sources.Node).init(std.heap.page_allocator), + source_node_pool: std.heap.MemoryPool(Sources.Node) = .empty, end_line: usize = 0, const sourceCompare = struct { @@ -742,7 +742,7 @@ pub const SourceMappings = struct { pub fn deinit(self: *SourceMappings, allocator: Allocator) void { self.files.deinit(allocator); - self.source_node_pool.deinit(); + self.source_node_pool.deinit(std.heap.page_allocator); } /// Find the node that 'contains' the `line`, i.e. the node's start_line is @@ -823,7 +823,7 @@ pub const SourceMappings = struct { .filename_offset = filename_offset, }; var entry = self.sources.getEntryFor(key); - var new_node = try self.source_node_pool.create(); + var new_node = try self.source_node_pool.create(std.heap.page_allocator); new_node.key = key; entry.set(new_node); } @@ -869,7 +869,7 @@ pub const SourceMappings = struct { .filename_offset = node.key.filename_offset, }; var entry = self.sources.getEntryFor(key); - var new_node = try self.source_node_pool.create(); + var new_node = try self.source_node_pool.create(std.heap.page_allocator); new_node.key = key; entry.set(new_node); node = new_node; diff --git a/lib/std/heap.zig b/lib/std/heap.zig index ebc0de26b8..c35e6f6684 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -25,10 +25,20 @@ pub const GeneralPurposeAllocatorConfig = DebugAllocatorConfig; /// Deprecated; to be removed after 0.14.0 is tagged. pub const GeneralPurposeAllocator = DebugAllocator; -const memory_pool = @import("heap/memory_pool.zig"); -pub const MemoryPool = memory_pool.MemoryPool; -pub const MemoryPoolAligned = memory_pool.MemoryPoolAligned; -pub const MemoryPoolExtra = memory_pool.MemoryPoolExtra; +/// A memory pool that can allocate objects of a single type very quickly. +/// Use this when you need to allocate a lot of objects of the same type, +/// because it outperforms general purpose allocators. +/// Functions that potentially allocate memory accept an `Allocator` parameter. +pub fn MemoryPool(comptime Item: type) type { + return memory_pool.Extra(Item, .{ .alignment = null }); +} +pub const memory_pool = @import("heap/memory_pool.zig"); + +/// Deprecated; use `memory_pool.Aligned`. +pub const MemoryPoolAligned = memory_pool.Aligned; +/// Deprecated; use `memory_pool.Extra`. +pub const MemoryPoolExtra = memory_pool.Extra; +/// Deprecated; use `memory_pool.Options`. pub const MemoryPoolOptions = memory_pool.Options; /// TODO Utilize this on Windows. diff --git a/lib/std/heap/memory_pool.zig b/lib/std/heap/memory_pool.zig index 2b201f2b54..30691ba129 100644 --- a/lib/std/heap/memory_pool.zig +++ b/lib/std/heap/memory_pool.zig @@ -1,26 +1,26 @@ const std = @import("../std.zig"); +const Allocator = std.mem.Allocator; const Alignment = std.mem.Alignment; +const MemoryPool = std.heap.MemoryPool; -const debug_mode = @import("builtin").mode == .Debug; - -pub const MemoryPoolError = error{OutOfMemory}; - -/// A memory pool that can allocate objects of a single type very quickly. -/// Use this when you need to allocate a lot of objects of the same type, -/// because It outperforms general purpose allocators. -pub fn MemoryPool(comptime Item: type) type { - return MemoryPoolAligned(Item, .of(Item)); +/// Deprecated. +pub fn Managed(comptime Item: type) type { + return ExtraManaged(Item, .{ .alignment = null }); } /// A memory pool that can allocate objects of a single type very quickly. /// Use this when you need to allocate a lot of objects of the same type, -/// because It outperforms general purpose allocators. -pub fn MemoryPoolAligned(comptime Item: type, comptime alignment: Alignment) type { - if (@alignOf(Item) == comptime alignment.toByteUnits()) { - return MemoryPoolExtra(Item, .{}); - } else { - return MemoryPoolExtra(Item, .{ .alignment = alignment }); - } +/// because it outperforms general purpose allocators. +/// Allocated items are aligned to `alignment`-byte addresses or `@alignOf(Item)` +/// if `alignment` is `null`. +/// Functions that potentially allocate memory accept an `Allocator` parameter. +pub fn Aligned(comptime Item: type, comptime alignment: Alignment) type { + return Extra(Item, .{ .alignment = alignment }); +} + +/// Deprecated. +pub fn AlignedManaged(comptime Item: type, comptime alignment: Alignment) type { + return ExtraManaged(Item, .{ .alignment = alignment }); } pub const Options = struct { @@ -34,64 +34,70 @@ pub const Options = struct { /// A memory pool that can allocate objects of a single type very quickly. /// Use this when you need to allocate a lot of objects of the same type, -/// because It outperforms general purpose allocators. -pub fn MemoryPoolExtra(comptime Item: type, comptime pool_options: Options) type { +/// because it outperforms general purpose allocators. +/// Functions that potentially allocate memory accept an `Allocator` parameter. +pub fn Extra(comptime Item: type, comptime pool_options: Options) type { + if (pool_options.alignment) |a| { + if (a.compare(.eq, .of(Item))) { + var new_options = pool_options; + new_options.alignment = null; + return Extra(Item, new_options); + } + } return struct { const Pool = @This(); + arena_state: std.heap.ArenaAllocator.State, + free_list: std.SinglyLinkedList, + /// Size of the memory pool items. This is not necessarily the same /// as `@sizeOf(Item)` as the pool also uses the items for internal means. pub const item_size = @max(@sizeOf(Node), @sizeOf(Item)); - // This needs to be kept in sync with Node. - const node_alignment: Alignment = .of(*anyopaque); - /// Alignment of the memory pool items. This is not necessarily the same /// as `@alignOf(Item)` as the pool also uses the items for internal means. - pub const item_alignment: Alignment = node_alignment.max(pool_options.alignment orelse .of(Item)); + pub const item_alignment: Alignment = .max(pool_options.alignment orelse .of(Item), .of(Node)); - const Node = struct { - next: ?*align(item_alignment.toByteUnits()) @This(), - }; - const NodePtr = *align(item_alignment.toByteUnits()) Node; + const Node = std.SinglyLinkedList.Node; const ItemPtr = *align(item_alignment.toByteUnits()) Item; - arena: std.heap.ArenaAllocator, - free_list: ?NodePtr = null, + /// A MemoryPool containing no elements. + pub const empty: Pool = .{ + .arena_state = .{}, + .free_list = .{}, + }; - /// Creates a new memory pool. - pub fn init(allocator: std.mem.Allocator) Pool { - return .{ .arena = std.heap.ArenaAllocator.init(allocator) }; - } - - /// Creates a new memory pool and pre-allocates `initial_size` items. - /// This allows the up to `initial_size` active allocations before a - /// `OutOfMemory` error happens when calling `create()`. - pub fn initPreheated(allocator: std.mem.Allocator, initial_size: usize) MemoryPoolError!Pool { - var pool = init(allocator); - errdefer pool.deinit(); - try pool.preheat(initial_size); + /// Creates a new memory pool and pre-allocates `num` items. + /// This allows up to `num` active allocations before an + /// `OutOfMemory` error might happen when calling `create()`. + pub fn initCapacity(allocator: Allocator, num: usize) Allocator.Error!Pool { + var pool: Pool = .empty; + errdefer pool.deinit(allocator); + try pool.addCapacity(allocator, num); return pool; } /// Destroys the memory pool and frees all allocated memory. - pub fn deinit(pool: *Pool) void { - pool.arena.deinit(); + pub fn deinit(pool: *Pool, allocator: Allocator) void { + pool.arena_state.promote(allocator).deinit(); pool.* = undefined; } - /// Preheats the memory pool by pre-allocating `size` items. - /// This allows up to `size` active allocations before an + pub fn toManaged(pool: Pool, allocator: Allocator) ExtraManaged(Item, pool_options) { + return .{ + .allocator = allocator, + .unmanaged = pool, + }; + } + + /// Pre-allocates `num` items and adds them to the memory pool. + /// This allows at least `num` active allocations before an /// `OutOfMemory` error might happen when calling `create()`. - pub fn preheat(pool: *Pool, size: usize) MemoryPoolError!void { + pub fn addCapacity(pool: *Pool, allocator: Allocator, num: usize) Allocator.Error!void { var i: usize = 0; - while (i < size) : (i += 1) { - const raw_mem = try pool.allocNew(); - const free_node = @as(NodePtr, @ptrCast(raw_mem)); - free_node.* = Node{ - .next = pool.free_list, - }; - pool.free_list = free_node; + while (i < num) : (i += 1) { + const memory = try pool.allocNew(allocator); + pool.free_list.prepend(@ptrCast(memory)); } } @@ -106,28 +112,29 @@ pub fn MemoryPoolExtra(comptime Item: type, comptime pool_options: Options) type /// be slower. /// /// NOTE: If `mode` is `free_all`, the function will always return `true`. - pub fn reset(pool: *Pool, mode: ResetMode) bool { + pub fn reset(pool: *Pool, allocator: Allocator, mode: ResetMode) bool { // TODO: Potentially store all allocated objects in a list as well, allowing to // just move them into the free list instead of actually releasing the memory. - const reset_successful = pool.arena.reset(mode); + var arena = pool.arena_state.promote(allocator); + defer pool.arena_state = arena.state; - pool.free_list = null; + const reset_successful = arena.reset(mode); + pool.free_list = .{}; return reset_successful; } /// Creates a new item and adds it to the memory pool. - pub fn create(pool: *Pool) !ItemPtr { - const node = if (pool.free_list) |item| blk: { - pool.free_list = item.next; - break :blk item; - } else if (pool_options.growable) - @as(NodePtr, @ptrCast(try pool.allocNew())) + /// `allocator` may be `undefined` if pool is not `growable`. + pub fn create(pool: *Pool, allocator: Allocator) Allocator.Error!ItemPtr { + const ptr: ItemPtr = if (pool.free_list.popFirst()) |node| + @ptrCast(@alignCast(node)) + else if (pool_options.growable) + @ptrCast(try pool.allocNew(allocator)) else return error.OutOfMemory; - const ptr = @as(ItemPtr, @ptrCast(node)); ptr.* = undefined; return ptr; } @@ -136,87 +143,238 @@ pub fn MemoryPoolExtra(comptime Item: type, comptime pool_options: Options) type /// Only pass items to `ptr` that were previously created with `create()` of the same memory pool! pub fn destroy(pool: *Pool, ptr: ItemPtr) void { ptr.* = undefined; - - const node = @as(NodePtr, @ptrCast(ptr)); - node.* = Node{ - .next = pool.free_list, - }; - pool.free_list = node; + pool.free_list.prepend(@ptrCast(ptr)); } - fn allocNew(pool: *Pool) MemoryPoolError!*align(item_alignment.toByteUnits()) [item_size]u8 { - const mem = try pool.arena.allocator().alignedAlloc(u8, item_alignment, item_size); - return mem[0..item_size]; // coerce slice to array pointer + fn allocNew(pool: *Pool, allocator: Allocator) Allocator.Error!*align(item_alignment.toByteUnits()) [item_size]u8 { + var arena = pool.arena_state.promote(allocator); + defer pool.arena_state = arena.state; + const memory = try arena.allocator().alignedAlloc(u8, item_alignment, item_size); + return memory[0..item_size]; + } + }; +} + +/// Deprecated. +pub fn ExtraManaged(comptime Item: type, comptime pool_options: Options) type { + if (pool_options.alignment) |a| { + if (a.compare(.eq, .of(Item))) { + var new_options = pool_options; + new_options.alignment = null; + return ExtraManaged(Item, new_options); + } + } + return struct { + const Pool = @This(); + + allocator: Allocator, + unmanaged: Unmanaged, + + pub const Unmanaged = Extra(Item, pool_options); + pub const item_size = Unmanaged.item_size; + pub const item_alignment = Unmanaged.item_alignment; + + const ItemPtr = Unmanaged.ItemPtr; + + /// Creates a new memory pool. + pub fn init(allocator: Allocator) Pool { + return Unmanaged.empty.toManaged(allocator); + } + + /// Creates a new memory pool and pre-allocates `num` items. + /// This allows up to `num` active allocations before an + /// `OutOfMemory` error might happen when calling `create()`. + pub fn initCapacity(allocator: Allocator, num: usize) Allocator.Error!Pool { + return (try Unmanaged.initCapacity(allocator, num)).toManaged(allocator); + } + + /// Destroys the memory pool and frees all allocated memory. + pub fn deinit(pool: *Pool) void { + pool.unmanaged.deinit(pool.allocator); + pool.* = undefined; + } + + /// Pre-allocates `num` items and adds them to the memory pool. + /// This allows at least `num` active allocations before an + /// `OutOfMemory` error might happen when calling `create()`. + pub fn addCapacity(pool: *Pool, num: usize) Allocator.Error!void { + return pool.unmanaged.addCapacity(pool.allocator, num); + } + + pub const ResetMode = Unmanaged.ResetMode; + + /// Resets the memory pool and destroys all allocated items. + /// This can be used to batch-destroy all objects without invalidating the memory pool. + /// + /// The function will return whether the reset operation was successful or not. + /// If the reallocation failed `false` is returned. The pool will still be fully + /// functional in that case, all memory is released. Future allocations just might + /// be slower. + /// + /// NOTE: If `mode` is `free_all`, the function will always return `true`. + pub fn reset(pool: *Pool, mode: ResetMode) bool { + return pool.unmanaged.reset(pool.allocator, mode); + } + + /// Creates a new item and adds it to the memory pool. + pub fn create(pool: *Pool) Allocator.Error!ItemPtr { + return pool.unmanaged.create(pool.allocator); + } + + /// Destroys a previously created item. + /// Only pass items to `ptr` that were previously created with `create()` of the same memory pool! + pub fn destroy(pool: *Pool, ptr: ItemPtr) void { + return pool.unmanaged.destroy(ptr); + } + + fn allocNew(pool: *Pool) Allocator.Error!*align(item_alignment) [item_size]u8 { + return pool.unmanaged.allocNew(pool.allocator); } }; } test "basic" { - var pool = MemoryPool(u32).init(std.testing.allocator); - defer pool.deinit(); + const a = std.testing.allocator; - const p1 = try pool.create(); - const p2 = try pool.create(); - const p3 = try pool.create(); + { + var pool: MemoryPool(u32) = .empty; + defer pool.deinit(a); - // Assert uniqueness - try std.testing.expect(p1 != p2); - try std.testing.expect(p1 != p3); - try std.testing.expect(p2 != p3); + const p1 = try pool.create(a); + const p2 = try pool.create(a); + const p3 = try pool.create(a); - pool.destroy(p2); - const p4 = try pool.create(); + // Assert uniqueness + try std.testing.expect(p1 != p2); + try std.testing.expect(p1 != p3); + try std.testing.expect(p2 != p3); - // Assert memory reuse - try std.testing.expect(p2 == p4); + pool.destroy(p2); + const p4 = try pool.create(a); + + // Assert memory reuse + try std.testing.expect(p2 == p4); + } + + { + var pool: Managed(u32) = .init(std.testing.allocator); + defer pool.deinit(); + + const p1 = try pool.create(); + const p2 = try pool.create(); + const p3 = try pool.create(); + + // Assert uniqueness + try std.testing.expect(p1 != p2); + try std.testing.expect(p1 != p3); + try std.testing.expect(p2 != p3); + + pool.destroy(p2); + const p4 = try pool.create(); + + // Assert memory reuse + try std.testing.expect(p2 == p4); + } } -test "preheating (success)" { - var pool = try MemoryPool(u32).initPreheated(std.testing.allocator, 4); - defer pool.deinit(); +test "initCapacity (success)" { + const a = std.testing.allocator; - _ = try pool.create(); - _ = try pool.create(); - _ = try pool.create(); + { + var pool: MemoryPool(u32) = try .initCapacity(a, 4); + defer pool.deinit(a); + + _ = try pool.create(a); + _ = try pool.create(a); + _ = try pool.create(a); + } + + { + var pool: Managed(u32) = try .initCapacity(a, 4); + defer pool.deinit(); + + _ = try pool.create(); + _ = try pool.create(); + _ = try pool.create(); + } } -test "preheating (failure)" { +test "initCapacity (failure)" { const failer = std.testing.failing_allocator; - try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initPreheated(failer, 5)); + try std.testing.expectError(error.OutOfMemory, MemoryPool(u32).initCapacity(failer, 5)); + try std.testing.expectError(error.OutOfMemory, Managed(u32).initCapacity(failer, 5)); } test "growable" { - var pool = try MemoryPoolExtra(u32, .{ .growable = false }).initPreheated(std.testing.allocator, 4); - defer pool.deinit(); + const a = std.testing.allocator; - _ = try pool.create(); - _ = try pool.create(); - _ = try pool.create(); - _ = try pool.create(); + { + var pool: Extra(u32, .{ .growable = false }) = try .initCapacity(a, 4); + defer pool.deinit(a); - try std.testing.expectError(error.OutOfMemory, pool.create()); + _ = try pool.create(a); + _ = try pool.create(a); + _ = try pool.create(a); + _ = try pool.create(a); + + try std.testing.expectError(error.OutOfMemory, pool.create(a)); + } + + { + var pool: ExtraManaged(u32, .{ .growable = false }) = try .initCapacity(a, 4); + defer pool.deinit(); + + _ = try pool.create(); + _ = try pool.create(); + _ = try pool.create(); + _ = try pool.create(); + + try std.testing.expectError(error.OutOfMemory, pool.create()); + } } test "greater than pointer default alignment" { const Foo = struct { data: u64 align(16), }; + const a = std.testing.allocator; - var pool = MemoryPool(Foo).init(std.testing.allocator); - defer pool.deinit(); + { + var pool: MemoryPool(Foo) = .empty; + defer pool.deinit(a); - const foo: *Foo = try pool.create(); - _ = foo; + const foo: *Foo = try pool.create(a); + pool.destroy(foo); + } + + { + var pool: Managed(Foo) = .init(a); + defer pool.deinit(); + + const foo: *Foo = try pool.create(); + pool.destroy(foo); + } } test "greater than pointer manual alignment" { const Foo = struct { data: u64, }; + const a = std.testing.allocator; - var pool = MemoryPoolAligned(Foo, .@"16").init(std.testing.allocator); - defer pool.deinit(); + { + var pool: Aligned(Foo, .@"16") = .empty; + defer pool.deinit(a); - const foo: *align(16) Foo = try pool.create(); - _ = foo; + const foo: *align(16) Foo = try pool.create(a); + pool.destroy(foo); + } + + { + var pool: AlignedManaged(Foo, .@"16") = .init(a); + defer pool.deinit(); + + const foo: *align(16) Foo = try pool.create(); + pool.destroy(foo); + } }