mirror of
https://github.com/oven-sh/bun
synced 2026-02-12 11:59:00 +00:00
Replace `catch bun.outOfMemory()`, which can accidentally catch non-OOM-related errors, with either `bun.handleOom` or a manual `catch |err| switch (err)`. (For internal tracking: fixes STAB-1070) --------- Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
172 lines
6.0 KiB
Zig
172 lines
6.0 KiB
Zig
/// This "Store" is a specialized memory allocation strategy very similar to an
|
|
/// arena, used for allocating expression and statement nodes during JavaScript
|
|
/// parsing and visiting. Allocations are grouped into large blocks, where each
|
|
/// block is treated as a fixed-buffer allocator. When a block runs out of
|
|
/// space, a new one is created; all blocks are joined as a linked list.
|
|
///
|
|
/// Similarly to an arena, you can call .reset() to reset state, reusing memory
|
|
/// across operations.
|
|
pub fn NewStore(comptime types: []const type, comptime count: usize) type {
|
|
const largest_size, const largest_align = brk: {
|
|
var largest_size = 0;
|
|
var largest_align = 1;
|
|
for (types) |T| {
|
|
if (@sizeOf(T) == 0) {
|
|
@compileError("NewStore does not support 0 size type: " ++ @typeName(T));
|
|
}
|
|
largest_size = @max(@sizeOf(T), largest_size);
|
|
largest_align = @max(@alignOf(T), largest_align);
|
|
}
|
|
break :brk .{ largest_size, largest_align };
|
|
};
|
|
|
|
const backing_allocator = bun.default_allocator;
|
|
|
|
const log = Output.scoped(.Store, .hidden);
|
|
|
|
return struct {
|
|
const Store = @This();
|
|
|
|
current: *Block,
|
|
debug_lock: std.debug.SafetyLock = .{},
|
|
|
|
pub const Block = struct {
|
|
pub const size = largest_size * count * 2;
|
|
pub const Size = std.math.IntFittingRange(0, size + largest_size);
|
|
|
|
buffer: [size]u8 align(largest_align),
|
|
bytes_used: Size = 0,
|
|
next: ?*Block = null,
|
|
|
|
pub inline fn zero(this: *Block) void {
|
|
// Avoid initializing the entire struct.
|
|
this.bytes_used = 0;
|
|
this.next = null;
|
|
}
|
|
|
|
pub fn tryAlloc(block: *Block, comptime T: type) ?*T {
|
|
const start = std.mem.alignForward(usize, block.bytes_used, @alignOf(T));
|
|
if (start + @sizeOf(T) > block.buffer.len) return null;
|
|
defer block.bytes_used = @intCast(start + @sizeOf(T));
|
|
|
|
// it's simpler to use @ptrCast, but as a sanity check, we also
|
|
// try to compute the slice. Zig will report an out of bounds
|
|
// panic if the null detection logic above is wrong
|
|
if (Environment.isDebug) {
|
|
_ = block.buffer[block.bytes_used..][0..@sizeOf(T)];
|
|
}
|
|
|
|
return @alignCast(@ptrCast(&block.buffer[start]));
|
|
}
|
|
};
|
|
|
|
const PreAlloc = struct {
|
|
metadata: Store,
|
|
first_block: Block,
|
|
|
|
pub inline fn zero(this: *PreAlloc) void {
|
|
// Avoid initializing the entire struct.
|
|
this.first_block.zero();
|
|
this.metadata.current = &this.first_block;
|
|
}
|
|
};
|
|
|
|
pub fn firstBlock(store: *Store) *Block {
|
|
return &@as(*PreAlloc, @fieldParentPtr("metadata", store)).first_block;
|
|
}
|
|
|
|
pub fn init() *Store {
|
|
log("init", .{});
|
|
// Avoid initializing the entire struct.
|
|
const prealloc = bun.handleOom(backing_allocator.create(PreAlloc));
|
|
prealloc.zero();
|
|
|
|
return &prealloc.metadata;
|
|
}
|
|
|
|
pub fn deinit(store: *Store) void {
|
|
log("deinit", .{});
|
|
var it = store.firstBlock().next; // do not free `store.head`
|
|
while (it) |next| {
|
|
if (Environment.isDebug or Environment.enable_asan)
|
|
@memset(next.buffer, undefined);
|
|
it = next.next;
|
|
backing_allocator.destroy(next);
|
|
}
|
|
|
|
const prealloc: PreAlloc = @fieldParentPtr("metadata", store);
|
|
bun.assert(&prealloc.first_block == store.head);
|
|
backing_allocator.destroy(prealloc);
|
|
}
|
|
|
|
pub fn reset(store: *Store) void {
|
|
log("reset", .{});
|
|
|
|
if (Environment.isDebug or Environment.enable_asan) {
|
|
var it: ?*Block = store.firstBlock();
|
|
while (it) |next| : (it = next.next) {
|
|
next.bytes_used = undefined;
|
|
@memset(&next.buffer, undefined);
|
|
}
|
|
}
|
|
|
|
store.current = store.firstBlock();
|
|
store.current.bytes_used = 0;
|
|
}
|
|
|
|
fn allocate(store: *Store, comptime T: type) *T {
|
|
comptime bun.assert(@sizeOf(T) > 0); // don't allocate!
|
|
comptime if (!supportsType(T)) {
|
|
@compileError("Store does not know about type: " ++ @typeName(T));
|
|
};
|
|
|
|
if (store.current.tryAlloc(T)) |ptr|
|
|
return ptr;
|
|
|
|
// a new block is needed
|
|
const next_block = if (store.current.next) |next| brk: {
|
|
next.bytes_used = 0;
|
|
break :brk next;
|
|
} else brk: {
|
|
const new_block = backing_allocator.create(Block) catch
|
|
bun.outOfMemory();
|
|
new_block.zero();
|
|
store.current.next = new_block;
|
|
break :brk new_block;
|
|
};
|
|
|
|
store.current = next_block;
|
|
|
|
return next_block.tryAlloc(T) orelse
|
|
unreachable; // newly initialized blocks must have enough space for at least one
|
|
}
|
|
|
|
pub inline fn append(store: *Store, comptime T: type, data: T) *T {
|
|
const ptr = store.allocate(T);
|
|
if (Environment.isDebug) {
|
|
log("append({s}) -> 0x{x}", .{ bun.meta.typeName(T), @intFromPtr(ptr) });
|
|
}
|
|
ptr.* = data;
|
|
return ptr;
|
|
}
|
|
|
|
pub fn lock(store: *Store) void {
|
|
store.debug_lock.lock();
|
|
}
|
|
|
|
pub fn unlock(store: *Store) void {
|
|
store.debug_lock.unlock();
|
|
}
|
|
|
|
fn supportsType(T: type) bool {
|
|
return std.mem.indexOfScalar(type, types, T) != null;
|
|
}
|
|
};
|
|
}
|
|
|
|
const std = @import("std");
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const Output = bun.Output;
|