Files
zig/lib/std/heap/BufferFirstAllocator.zig
T

166 lines
5.4 KiB
Zig

//! An allocator that attempts to allocate from the given buffer, falling back to
//! `fallback_allocator` if this fails.
const std = @import("../std.zig");
const heap = std.heap;
const testing = std.testing;
const Alignment = std.mem.Alignment;
const Allocator = std.mem.Allocator;
const FixedBufferAllocator = std.heap.FixedBufferAllocator;
const BufferFirstAllocator = @This();
fallback_allocator: Allocator,
fixed_buffer_allocator: FixedBufferAllocator,
pub fn init(buffer: []u8, fallback_allocator: Allocator) BufferFirstAllocator {
return .{
.fallback_allocator = fallback_allocator,
.fixed_buffer_allocator = .init(buffer),
};
}
pub fn allocator(self: *BufferFirstAllocator) Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
},
};
}
fn alloc(
ctx: *anyopaque,
len: usize,
alignment: Alignment,
ra: usize,
) ?[*]u8 {
const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx));
return FixedBufferAllocator.alloc(&self.fixed_buffer_allocator, len, alignment, ra) orelse
return self.fallback_allocator.rawAlloc(len, alignment, ra);
}
fn resize(
ctx: *anyopaque,
buf: []u8,
alignment: Alignment,
new_len: usize,
ra: usize,
) bool {
const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx));
if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
return FixedBufferAllocator.resize(&self.fixed_buffer_allocator, buf, alignment, new_len, ra);
} else {
return self.fallback_allocator.rawResize(buf, alignment, new_len, ra);
}
}
fn remap(
context: *anyopaque,
memory: []u8,
alignment: Alignment,
new_len: usize,
return_address: usize,
) ?[*]u8 {
const self: *BufferFirstAllocator = @ptrCast(@alignCast(context));
if (self.fixed_buffer_allocator.ownsPtr(memory.ptr)) {
return FixedBufferAllocator.remap(&self.fixed_buffer_allocator, memory, alignment, new_len, return_address);
} else {
return self.fallback_allocator.rawRemap(memory, alignment, new_len, return_address);
}
}
fn free(
ctx: *anyopaque,
buf: []u8,
alignment: Alignment,
ra: usize,
) void {
const self: *BufferFirstAllocator = @ptrCast(@alignCast(ctx));
if (self.fixed_buffer_allocator.ownsPtr(buf.ptr)) {
return FixedBufferAllocator.free(&self.fixed_buffer_allocator, buf, alignment, ra);
} else {
return self.fallback_allocator.rawFree(buf, alignment, ra);
}
}
test "BufferFirstAllocator" {
// Buffer first specific tests
{
var buffer: [10]u8 = undefined;
var bfa_state: BufferFirstAllocator = .init(&buffer, std.testing.allocator);
const bfa = bfa_state.allocator();
// We're under the limit, so we should be allocated in the buffer
const txt0 = "hellowrld";
const buf0 = try bfa.create(@TypeOf(txt0.*));
buf0.* = txt0.*;
try testing.expect(bfa_state.fixed_buffer_allocator.ownsPtr(buf0.ptr));
// We're now over the limit, so we should be allocated from the fallback
const txt1 = "test!";
const buf1 = try bfa.create(@TypeOf(txt1.*));
buf1.* = txt1.*;
try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf1.ptr));
// Free the allocation that took up space in the buffer
try testing.expectEqualStrings(txt0, buf0);
bfa.destroy(buf0);
// The next allocation would go in the buffer, but it's too big so it doesn't
const txt2 = "qwertyqwerty";
const buf2 = try bfa.create(@TypeOf(txt2.*));
buf2.* = txt2.*;
try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf2.ptr));
// The next allocation is smaller and fits in the buffer
const txt3 = "dvorak";
const buf3 = try bfa.create(@TypeOf(txt3.*));
buf3.* = txt3.*;
try testing.expect(bfa_state.fixed_buffer_allocator.ownsPtr(buf3.ptr));
// The remainder in the buffer is too small for the following allocation so it falls back
const txt4 = "moretext";
const buf4 = try bfa.create(@TypeOf(txt4.*));
buf4.* = txt4.*;
try testing.expect(!bfa_state.fixed_buffer_allocator.ownsPtr(buf4.ptr));
// Check equality on the remaining buffers and free them
try testing.expectEqualStrings(txt1, buf1);
bfa.destroy(buf1);
try testing.expectEqualStrings(txt2, buf2);
bfa.destroy(buf2);
try testing.expectEqualStrings(txt3, buf3);
bfa.destroy(buf3);
try testing.expectEqualStrings(txt4, buf4);
bfa.destroy(buf4);
try testing.expectEqual(0, bfa_state.fixed_buffer_allocator.end_index);
}
// Standard allocator tests
{
var buf: [4096]u8 = undefined;
{
var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
try heap.testAllocator(bfa.allocator());
}
{
var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
try heap.testAllocatorAligned(bfa.allocator());
}
{
var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
try heap.testAllocatorLargeAlignment(bfa.allocator());
}
{
var bfa: BufferFirstAllocator = .init(&buf, std.testing.allocator);
try heap.testAllocatorAlignedShrink(bfa.allocator());
}
}
}