Ok, I gave it a try this morning, instead. Please critique. Tests at bottom, though not as comprehensive as I’d like yet. Thank you!
const mem = std.mem;
const Allocator = mem.Allocator;
const FixedBufferAllocator = std.heap.FixedBufferAllocator;
pub const StackFirstAllocator = struct {
primary: FixedBufferAllocator,
secondary: Allocator,
pub fn init(buffer: []u8, secondary_allocator: Allocator) StackFirstAllocator {
return .{
.primary = .init(buffer),
.secondary = secondary_allocator,
};
}
pub fn allocator(self: *StackFirstAllocator) Allocator {
return .{
.ptr = self,
.vtable = &.{
.alloc = alloc,
.resize = resize,
.remap = remap,
.free = free,
},
};
}
pub fn alloc(ctx: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 {
const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
return FixedBufferAllocator.alloc(&self.primary, len, alignment, ret_addr) orelse
self.secondary.rawAlloc(len, alignment, ret_addr);
}
pub fn resize(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) bool {
const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
return if (self.primary.ownsPtr(memory.ptr))
FixedBufferAllocator.resize(&self.primary, memory, alignment, new_len, ret_addr) else
self.secondary.rawResize(memory, alignment, new_len, ret_addr);
}
pub fn remap(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, new_len: usize, ret_addr: usize) ?[*]u8 {
const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
return if (self.primary.ownsPtr(memory.ptr))
FixedBufferAllocator.remap(&self.primary, memory, alignment, new_len, ret_addr) else
self.secondary.rawRemap(memory, alignment, new_len, ret_addr);
}
pub fn free(ctx: *anyopaque, memory: []u8, alignment: mem.Alignment, ret_addr: usize) void {
const self: *StackFirstAllocator = @ptrCast(@alignCast(ctx));
return if (self.primary.ownsPtr(memory.ptr))
FixedBufferAllocator.free(&self.primary, memory, alignment, ret_addr) else
self.secondary.rawFree(memory, alignment, ret_addr);
}
pub fn reset(self: *StackFirstAllocator) void {
self.primary.reset();
}
};
test "sfa" {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var buffer: [10]u8 = undefined;
var sfa = StackFirstAllocator.init(&buffer, arena.allocator());
const expect = std.testing.expect;
const expectEqualStrings = std.testing.expectEqualStrings;
const allocator = sfa.allocator();
const txt = "0123456789";
const dest = try allocator.alloc(u8, txt.len);
@memcpy(dest, txt);
std.debug.print("In stack: {s}\n", .{dest});
try expectEqualStrings(txt, dest);
try expect(sfa.primary.ownsPtr(dest.ptr));
const txt2 = "abcde";
const dest2 = try allocator.alloc(u8, txt2.len);
@memcpy(dest2, txt2);
std.debug.print("In heap: {s}\n", .{dest2});
try expectEqualStrings(txt2, dest2);
try expect(!sfa.primary.ownsPtr(dest2.ptr));
sfa.reset();
//NOTE: this will NOT reset the secondary allocator - arena.reset() would have to be called here explicitly if wanted
const txt3 = "0123";
const dest3 = try allocator.alloc(u8, txt3.len);
@memcpy(dest3, txt3);
std.debug.print("In stack: {s}\n", .{dest3});
try expectEqualStrings(txt3, dest3);
try expect(sfa.primary.ownsPtr(dest3.ptr));
}