Last active
September 24, 2022 16:05
-
-
Save jumpnbrownweasel/5129912b13ce85b88fe9feb0bc029f32 to your computer and use it in GitHub Desktop.
Alloc an arena in the arena itself to avoid common UB mistakes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const expectEqual = std.testing.expectEqual; | |
/// Called instead of ArenaAllocator.init to ensure that the arena allocator | |
/// does not become invalid when the arena struct is moved. | |
/// | |
/// The ArenaAllocator is placed in the arena itself, and therefore the returned | |
/// pointer to the arena is stable. This means the allocator returned by | |
/// arena.allocator(), which contains a pointer to the arena struct, is also | |
/// stable. | |
/// | |
/// This avoids a common error when calling ArenaAllocator.init, which returns | |
/// the arena struct. If arena.allocator() is called using the returned arena | |
/// struct, and then the arena struct is moved, the allocator that was returned | |
/// by arena.allocator() will be invalid since it contains a pointer to the old | |
/// location of the arena struct. A workaround for this is avoid saving the | |
/// allocator returned by arena.allocator(), and instead call arena.allocator() | |
/// each time it is needed. By allocating the arena struct in the arena itself, | |
/// this workaround is no longer needed. | |
/// | |
/// The potential drawbacks of this approach are that an allocation must be | |
/// performed in the arena initially to hold the arena struct, and the arena | |
/// struct cannot be stored on the stack or elsewhere. | |
/// | |
/// WARNING: When calling allocArena instead of ArenaAllocator.init, freeArena | |
/// must be called instead ArenaAllocator.deinit. Or, the arena struct must be | |
/// moved to the stack before calling ArenaAllocator.deinit, which is what | |
/// freeArena does. | |
/// | |
pub fn allocArena( | |
backing: std.mem.Allocator, | |
) error{OutOfMemory}!*std.heap.ArenaAllocator { | |
// | |
// Allocate memory for the arena struct using the arena itself, and copy the | |
// arena struct into this allocated/stable memory. | |
// | |
var arena = std.heap.ArenaAllocator.init(backing); | |
var arena_ptr = try arena.allocator().create(std.heap.ArenaAllocator); | |
arena_ptr.* = arena; | |
return arena_ptr; | |
} | |
/// Called instead of ArenaAllocator.deinit when ArenaAllocator.init was used | |
/// to create the arena. | |
/// | |
pub fn freeArena(arena: *std.heap.ArenaAllocator) void { | |
// | |
// Copy the arena to the stack so it isn't destroyed while freeing its own | |
// memory. | |
// | |
var arena_copy = arena.*; | |
arena_copy.deinit(); | |
} | |
test "allocArena" { | |
var list_arena = try ArrayListArena(i32).init(std.testing.allocator); | |
defer list_arena.deinit(); | |
try list_arena.list.append(123); | |
try expectEqual(@as(i32, 123), list_arena.list.items[0]); | |
} | |
pub fn ArrayListArena(comptime T: type) type { | |
return struct { | |
const Self = @This(); | |
arena: *std.heap.ArenaAllocator, | |
list: *std.ArrayList(T), | |
pub fn init(backing: std.mem.Allocator) error{OutOfMemory}!Self { | |
const arena = try allocArena(backing); | |
errdefer freeArena(arena); | |
// | |
// Since we are using allocArena, the arena pointer is stable and | |
// therefore arena.allocator() is stable and can be given to the | |
// ArrayList safely. If we used an arena struct allocated on the | |
// stack here instead, allocations using arena.allocator() would | |
// cause UB. | |
// | |
const list = try arena.allocator().create(std.ArrayList(T)); | |
list.* = std.ArrayList(T).init(arena.allocator()); | |
// | |
// Note that allocating the array list struct in the arena isn't | |
// necessary -- it could also be an embedded struct field here if | |
// desired. | |
// | |
return Self{.arena = arena, .list = list}; | |
} | |
pub fn deinit(self: *Self) void { | |
// Calling self.list.deinit() is unnecessary. | |
freeArena(self.arena); | |
} | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment