tiny.bumpalo.BumpAllocator
Defined in tiny.bumpalo.
Source
Source: lib/bumpalo/src/bump.zig:32
zig
pub fn BumpAllocator(comptime min_alignment: usize) type { comptime { if (!std.math.isPowerOfTwo(min_alignment)) { @compileError("min_alignment must be a power of two"); } if (min_alignment > chunk_alignment) { @compileError("min_alignment may not be larger than chunk_alignment"); } } return struct { const chunk_header_alignment = @max(chunk_alignment, @alignOf(Chunk)); const minimum_alignment = min_alignment; const metadata_size = std.mem.alignForward(usize, @sizeOf(Chunk), chunk_alignment); const overhead = std.mem.alignForward(usize, malloc_overhead + metadata_size, chunk_alignment); pub const default_chunk_capacity = first_allocation_goal - overhead; backing_allocator: Allocator, current: ?*Chunk = null, backing_data_capacity_limit: ?usize = null, chunk_growth_goal_bytes: ?usize = null, caller_root: CallerRootState = .none, observation_id: if (observe.enabled) u64 else void, observation_generation: if (observe.enabled) u64 else void = if (observe.enabled) 0 else {}, const Self = @This(); pub const ChunkIterator: type = chunk_mod.Iterator; pub const ResetMode: type = ResetPolicy; pub fn init(backing_allocator: Allocator) Self { return .{ .backing_allocator = backing_allocator, .observation_id = if (comptime observe.enabled) observe.producerId() else {}, }; } pub inline fn observationId(arena: *const Self) u64 { if (comptime !observe.enabled) return 0; return arena.observation_id; } pub inline fn observationIdentity( arena: *const Self, ) observe.Identity { if (comptime !observe.enabled) { return .{ .producer_id = 0, .producer = .bump, .generation = 0, .owner_cookie = 0, }; } return observe.Identity.movable( arena.observation_id, .bump, arena.observation_generation, ); } pub fn initCapacity(backing_allocator: Allocator, capacity: usize) Allocator.Error!Self { var arena = Self.init(backing_allocator); if (capacity != 0) { arena.current = try arena.newChunk(capacity, capacity, .@"1", null, null); assert(arena.queryCapacity() >= capacity); } return arena; } pub fn deinit(arena: *Self) void { var span = observe.beginLifecycle( arena.observationIdentity(), .end, .deinit, @returnAddress(), ); arena.assertCallerRootInvariant(); chunk_mod.destroyList(arena.backing_allocator, arena.current); arena.current = null; arena.caller_root = .none; span.finish(.{ .succeeded = true }); } pub fn allocator(arena: *Self) Allocator { const Table = allocator_vtable.VTable(Self); return .{ .ptr = arena, .vtable = &Table.vtable, }; } pub fn minAlign(_: *const Self) usize { return min_alignment; } pub fn backingDataCapacityLimit(arena: *const Self) ?usize { return arena.backing_data_capacity_limit; } pub fn setBackingDataCapacityLimit(arena: *Self, limit: ?usize) void { arena.backing_data_capacity_limit = limit; } pub fn chunkGrowthGoal(arena: *const Self) ?usize { return arena.chunk_growth_goal_bytes; } pub fn setChunkGrowthGoal(arena: *Self, goal: ?usize) void { if (goal) |bytes| assert(bytes >= default_chunk_capacity); arena.chunk_growth_goal_bytes = goal; } pub fn reset(arena: *Self, mode: ResetMode) bool { var span = observe.beginLifecycle( arena.observationIdentity(), .invalidate, .reset, @returnAddress(), ); const succeeded = switch (mode) { .free_all => blk: { arena.freeAllChunks(); break :blk true; }, .retain_current => blk: { arena.retainCurrentChunk(); break :blk true; }, .retain_capacity => arena.retainSingleChunk( arena.queryCapacity(), null, ), .retain_with_limit => |limit| arena.retainSingleChunk( @min(arena.queryCapacity(), limit), limit, ), }; span.finish(.{ .succeeded = succeeded }); arena.advanceObservationGeneration(); return succeeded; } pub fn queryCapacity(arena: *const Self) usize { const current = arena.current orelse return 0; return current.total_capacity; } pub fn queryCallerDataCapacity(arena: *const Self) usize { return switch (arena.caller_root) { .linked => |root| root.capacity, .none, .detached => 0, }; } pub fn queryBackingDataCapacity(arena: *const Self) usize { const capacity = arena.queryCapacity(); const caller_capacity = arena.queryCallerDataCapacity(); assert(caller_capacity <= capacity); return capacity - caller_capacity; } pub fn queryUsedCapacity(arena: *const Self) usize { var used: usize = 0; var chunk = arena.current; while (chunk) |c| : (chunk = c.previous) { used += c.usedSlice().len; } return used; } pub fn queryCurrentChunkAvailable(arena: *const Self) usize { const current = arena.current orelse return 0; return current.remaining(); } pub fn queryCapacityIncludingMetadata(arena: *const Self) usize { var bytes: usize = 0; var chunk = arena.current; while (chunk) |c| : (chunk = c.previous) { bytes += chunk_mod.allocationFootprint(c); } return bytes; } pub fn clearAndFree(arena: *Self) void { var span = observe.beginLifecycle( arena.observationIdentity(), .invalidate, .clear_and_free, @returnAddress(), ); arena.freeAllChunks(); span.finish(.{ .succeeded = true }); arena.advanceObservationGeneration(); } pub fn clearRetainingCurrent(arena: *Self) void { var span = observe.beginLifecycle( arena.observationIdentity(), .invalidate, .clear_retaining_current, @returnAddress(), ); arena.retainCurrentChunk(); span.finish(.{ .succeeded = true }); arena.advanceObservationGeneration(); } pub fn clearRetainingLargest(arena: *Self) void { var span = observe.beginLifecycle( arena.observationIdentity(), .invalidate, .clear_retaining_largest, @returnAddress(), ); arena.retainLargestChunk(); span.finish(.{ .succeeded = true }); arena.advanceObservationGeneration(); } pub fn clearRetainingCapacity(arena: *Self) bool { var span = observe.beginLifecycle( arena.observationIdentity(), .invalidate, .clear_retaining_capacity, @returnAddress(), ); const succeeded = arena.retainSingleChunk( arena.queryCapacity(), null, ); span.finish(.{ .succeeded = succeeded }); arena.advanceObservationGeneration(); return succeeded; } pub fn clearRetainingCapacityLimit(arena: *Self, limit: usize) bool { var span = observe.beginLifecycle( arena.observationIdentity(), .invalidate, .clear_retaining_capacity_limit, @returnAddress(), ); const succeeded = arena.retainSingleChunk( @min(arena.queryCapacity(), limit), limit, ); span.finish(.{ .succeeded = succeeded }); arena.advanceObservationGeneration(); return succeeded; } fn advanceObservationGeneration(arena: *Self) void { if (comptime !observe.enabled) return; arena.observation_generation = std.math.add( u64, arena.observation_generation, 1, ) catch @panic("allocator observation generation exhausted"); } fn freeAllChunks(arena: *Self) void { arena.assertCallerRootInvariant(); chunk_mod.destroyList(arena.backing_allocator, arena.current); arena.current = null; arena.restoreCallerRoot(); arena.assertCallerRootInvariant(); } fn retainCurrentChunk(arena: *Self) void { arena.assertCallerRootInvariant(); const current = arena.current orelse return; chunk_mod.destroyList(arena.backing_allocator, current.previous); current.previous = null; current.setCursor(current.dataEnd()); current.total_capacity = current.capacity; switch (arena.caller_root) { .linked => |root| { if (current != root) arena.caller_root = .{ .detached = root }; }, .none, .detached => {}, } assert(current.remaining() == current.capacity); assert(arena.queryCapacity() == arena.queryCurrentChunkAvailable()); arena.assertCallerRootInvariant(); } fn retainLargestChunk(arena: *Self) void { switch (arena.caller_root) { .none => {}, .linked, .detached => { arena.freeAllChunks(); return; }, } const current = arena.current orelse return; var retained = current; var candidate = current.previous; while (candidate) |chunk| : (candidate = chunk.previous) { if (chunk.capacity > retained.capacity) retained = chunk; } chunk_mod.destroyUntil(arena.backing_allocator, current, retained); chunk_mod.destroyList(arena.backing_allocator, retained.previous); retained.previous = null; retained.setCursor(retained.dataEnd()); retained.total_capacity = retained.capacity; arena.current = retained; assert(retained.remaining() == retained.capacity); assert(arena.queryCapacity() == arena.queryCurrentChunkAvailable()); } fn retainSingleChunk(arena: *Self, requested_capacity: usize, max_capacity: ?usize) bool { arena.assertCallerRootInvariant(); const bounded_capacity = if (max_capacity) |limit| @min(requested_capacity, limit) else requested_capacity; if (bounded_capacity == 0) { arena.clearLiveAndDetachCallerRoot(); arena.assertCallerRootInvariant(); return true; } if (max_capacity) |limit| { const aligned_limit = std.mem.alignBackward( usize, limit, @max(chunk_header_alignment, min_alignment), ); if (aligned_limit == 0) { arena.clearLiveAndDetachCallerRoot(); arena.assertCallerRootInvariant(); return true; } } assert(bounded_capacity > 0); if (arena.current) |current| { if (current.previous == null and current.capacity >= bounded_capacity) { if (max_capacity == null or current.capacity <= max_capacity.?) { current.setCursor(current.dataEnd()); current.total_capacity = current.capacity; assert(current.remaining() == current.capacity); arena.assertCallerRootInvariant(); return true; } } } arena.clearLiveAndDetachCallerRoot(); const backing_capacity_remaining = arena.backingDataCapacityRemaining(); if (backing_capacity_remaining) |remaining| { const aligned_remaining = std.mem.alignBackward( usize, remaining, @max(chunk_header_alignment, min_alignment), ); if (max_capacity != null and aligned_remaining == 0) { arena.assertCallerRootInvariant(); return true; } } arena.current = arena.newChunkBounded( bounded_capacity, if (max_capacity == null) bounded_capacity else 0, .@"1", null, max_capacity, backing_capacity_remaining, ) catch { arena.restoreCallerRoot(); arena.assertCallerRootInvariant(); return false; }; arena.assertCallerRootInvariant(); return true; } fn clearLiveAndDetachCallerRoot(arena: *Self) void { chunk_mod.destroyList(arena.backing_allocator, arena.current); arena.current = null; switch (arena.caller_root) { .linked => |root| arena.caller_root = .{ .detached = root }, .none, .detached => {}, } } fn restoreCallerRoot(arena: *Self) void { switch (arena.caller_root) { .none => {}, .linked, .detached => |root| { root.previous = null; root.setCursor(root.dataEnd()); root.total_capacity = root.capacity; arena.current = root; arena.caller_root = .{ .linked = root }; }, } } fn assertCallerRootInvariant(arena: *const Self) void { switch (arena.caller_root) { .none => {}, .linked => |root| { assert(root.previous == null); assert(arena.chunkReachable(root)); }, .detached => |root| { assert(root.previous == null); assert(!arena.chunkReachable(root)); }, } } fn chunkReachable(arena: *const Self, target: *Chunk) bool { var chunk = arena.current; while (chunk) |current| : (chunk = current.previous) { if (current == target) return true; } return false; } pub fn iterAllocatedChunks(arena: *Self) ChunkIterator { return .{ .next_chunk = arena.current }; } pub inline fn allocValue(arena: *Self, value: anytype) Allocator.Error!*@TypeOf(value) { const T = @TypeOf(value); const ptr = try arena.create(T); ptr.* = value; return ptr; } pub inline fn create(arena: *Self, comptime T: type) Allocator.Error!*T { if (@sizeOf(T) == 0) { const address = comptime Alignment.of(T).backward(std.math.maxInt(usize)); return @ptrFromInt(address); } const raw = try arena.allocBytes(@sizeOf(T), .of(T)); return @ptrCast(@alignCast(raw)); } pub inline fn alloc(arena: *Self, comptime T: type, len: usize) Allocator.Error![]T { if (@sizeOf(T) == 0) { const address = comptime Alignment.of(T).backward(std.math.maxInt(usize)); const ptr: [*]T = @ptrFromInt(address); return ptr[0..len]; } const byte_len = if (comptime @sizeOf(T) == 1) len else std.math.mul(usize, @sizeOf(T), len) catch return error.OutOfMemory; const raw = try arena.allocBytes(byte_len, .of(T)); const ptr: [*]T = @ptrCast(@alignCast(raw)); return ptr[0..len]; } pub inline fn dupe(arena: *Self, comptime T: type, source: []const T) Allocator.Error![]T { const dest = try arena.alloc(T, source.len); @memcpy(dest, source); return dest; } pub inline fn dupeSentinel( arena: *Self, comptime T: type, source: []const T, comptime sentinel: T, ) Allocator.Error![:sentinel]T { const dest = try arena.alloc(T, source.len + 1); @memcpy(dest[0..source.len], source); dest[source.len] = sentinel; return dest[0..source.len :sentinel]; } pub inline fn dupeZ(arena: *Self, comptime T: type, source: []const T) Allocator.Error![:0]T { return arena.dupeSentinel(T, source, 0); } pub inline fn allocFill(arena: *Self, comptime T: type, len: usize, value: T) Allocator.Error![]T { const dest = try arena.alloc(T, len); @memset(dest, value); return dest; } pub inline fn allocBytes(arena: *Self, len: usize, alignment: Alignment) Allocator.Error![*]u8 { return arena.tryAllocBytes(len, alignment) orelse error.OutOfMemory; } inline fn tryAllocBytes(arena: *Self, len: usize, alignment: Alignment) ?[*]u8 { if (len == 0) { const effective_align = @max(alignment.toByteUnits(), min_alignment); const address = Alignment.fromByteUnits(effective_align).backward(std.math.maxInt(usize)); return @ptrFromInt(address); } if (arena.tryAllocBytesFast(len, alignment)) |ptr| return ptr; return arena.allocBytesSlow(len, alignment); } inline fn tryAllocBytesFast(arena: *Self, len: usize, alignment: Alignment) ?[*]u8 { const current = arena.current orelse { @branchHint(.unlikely); return null; }; return allocFast(current, len, alignment, min_alignment); } inline fn allocFast(current: *Chunk, len: usize, alignment: Alignment, comptime min_align: usize) ?[*]u8 { return chunk_mod.allocFast(current, len, alignment, min_align); } fn allocBytesSlow(arena: *Self, len: usize, alignment: Alignment) ?[*]u8 { assert(len != 0); const remaining_limit = arena.backingDataCapacityRemaining(); const previous = arena.current; const min_new_capacity = @max(len, default_chunk_capacity); var base_capacity = min_new_capacity; if (previous) |chunk| { const effective_align = @max(alignment.toByteUnits(), min_alignment); if (std.mem.isAligned(@intFromPtr(chunk.cursor()), effective_align)) { base_capacity = @max(chunk.capacity *| 2, min_new_capacity); } } if (arena.chunk_growth_goal_bytes) |goal| { base_capacity = @min(base_capacity, @max(goal, min_new_capacity)); } if (remaining_limit) |remaining| { if (remaining < len) return null; if (base_capacity > remaining) base_capacity = remaining; } if (base_capacity < len) return null; const new_chunk = arena.newChunk( base_capacity, len, alignment, previous, remaining_limit, ) catch return null; arena.current = new_chunk; const ptr = allocFast(new_chunk, len, alignment, min_alignment).?; assert(std.mem.isAligned(@intFromPtr(ptr), @max(alignment.toByteUnits(), min_alignment))); return ptr; } fn backingDataCapacityRemaining(arena: *const Self) ?usize { const limit = arena.backing_data_capacity_limit orelse return null; const allocated = arena.queryBackingDataCapacity(); if (allocated >= limit) return 0; return limit - allocated; } fn newChunk( arena: *Self, requested_capacity: usize, requested_len: usize, requested_alignment: Alignment, previous: ?*Chunk, backing_data_capacity_remaining: ?usize, ) Allocator.Error!*Chunk { return arena.newChunkBounded( requested_capacity, requested_len, requested_alignment, previous, null, backing_data_capacity_remaining, ); } fn newChunkBounded( arena: *Self, requested_capacity: usize, requested_len: usize, requested_alignment: Alignment, previous: ?*Chunk, max_capacity: ?usize, backing_data_capacity_remaining: ?usize, ) Allocator.Error!*Chunk { assert(requested_len <= requested_capacity); return chunk_mod.create(arena.backing_allocator, .{ .requested_capacity = requested_capacity, .requested_len = requested_len, .requested_alignment = requested_alignment, .previous = previous, .max_capacity = max_capacity, .min_alignment = min_alignment, .chunk_header_alignment = chunk_header_alignment, .overhead = overhead, .typical_page_size = typical_page_size, .backing_data_capacity_remaining = backing_data_capacity_remaining, }); } pub fn initBuffer(backing_allocator: Allocator, buffer: []u8) Allocator.Error!Self { var arena = Self.init(backing_allocator); if (buffer.len != 0) { const initial = try Self.newChunkInBuffer(buffer, null); arena.current = initial; arena.caller_root = .{ .linked = initial }; arena.assertCallerRootInvariant(); } return arena; } fn newChunkInBuffer(buffer: []u8, previous: ?*Chunk) Allocator.Error!*Chunk { return chunk_mod.createInBuffer(buffer, .{ .previous = previous, .min_alignment = min_alignment, .chunk_header_alignment = chunk_header_alignment, }) orelse error.OutOfMemory; } };}Source: lib/bumpalo/src/root.zig:35
zig
pub const BumpAllocator = bump_impl.BumpAllocator;Audit
| Definitions | 1 |
|---|---|
| Public names | 1 |
| Members | 0 |
| Version | 26.7.0 |
| Revision | daab053ee433 |