Files
spl/stage2/utils/arena.spl
T

212 lines
6.1 KiB
Plaintext

// Expect retcode = 0
// 分块 bump 分配器:按块分配、块内顺序推进,只在 drop 时整体释放。
// 适合 AST/IR 这类生命周期一致的批量小对象;不支持单独 free。
// 分配失败通过 ArenaResult 区分原因:size==0/非法对齐/内存不足/地址溢出。
// 失败的 alloc 不改变已有分配。
type .{ .Vec } = @import("vec.spl");
type .{ .Error, .Kind, .Status } = @import("error.spl");
type .{ .checked_add, .align_up } = @import("memory.spl");
type Mem = @import("../sys/mem.spl");
type Chunk = struct {
base: *u8,
size: usize,
used: usize,
}
type ArenaResult = enum {
Ok: *u8,
Err: Error,
}
type Arena = @self;
chunk_size: usize,
chunks: Vec, // 元素类型 Chunk
current: usize,
fn new(chunk_size: usize): Arena {
var cs = chunk_size;
if cs == 0 { cs = 4096; }
ret Arena {
.chunk_size = cs,
.chunks = Vec.blank(@sizeof(Chunk), 8),
.current = 0,
};
}
fn chunk_at(self: *Arena, index: usize): *Chunk {
match Vec.get(&self.chunks, index) {
.Ok(ptr) => { ret ptr as *Chunk; },
// 越界/无效 Arena 统一返回 null;调用方负责判空,无资源需要释放。
.Err(error) => { ret null; },
}
ret null;
}
fn push_chunk(self: *Arena, size: usize): Status {
var base = Mem.alloc(size) as *u8;
if base == null { ret .Err(Error.make(.Overflow)); }
var c = Chunk { .base = base, .size = size, .used = 0 };
match Vec.push(&self.chunks, &c as *u8) {
.Ok => {},
.Err(error) => { Mem.free(base as *any); ret .Err(error); },
}
self.current = self.chunks.len - 1;
ret .Ok;
}
// 分配 size 字节,地址按 align(非零二次幂)对齐。失败返回 ArenaResult.Err。
fn alloc(self: *Arena, size: usize, align: usize): ArenaResult {
if size == 0 { ret .Err(Error.make(.InvalidArgument)); }
if align == 0 { align = 1; }
while true {
if self.current >= self.chunks.len {
var want = self.chunk_size;
var need: usize = 0;
match checked_add(size, align) {
.Ok(value) => { need = value; },
.Err(error) => { ret .Err(error); },
}
if need > want { want = need; }
match push_chunk(self, want) {
.Ok => {},
.Err(error) => { ret .Err(error); },
}
}
var c = chunk_at(self, self.current);
if c == null { ret .Err(Error.make(.InvalidArgument)); }
var addr = c.base as usize;
var used_addr: usize = 0;
match checked_add(addr, c.used) {
.Ok(value) => { used_addr = value; },
.Err(error) => { ret .Err(error); },
}
var aligned: usize = 0;
match align_up(used_addr, align) {
.Ok(value) => { aligned = value; },
.Err(error) => { ret .Err(error); }, // 非二次幂对齐或地址溢出
}
var offset = aligned - addr;
if offset <= c.size && size <= c.size - offset {
c.used = offset + size;
ret .Ok(aligned as *u8);
}
self.current = self.current + 1;
}
ret .Err(Error.make(.OutOfRange)); // 不可达:循环内所有路径均已返回
}
fn chunk_count(self: Arena): usize {
ret self.chunks.len;
}
// 复用已分配的块,不释放内存。
fn reset(self: *Arena) {
var i: usize = 0;
while i < self.chunks.len {
var c = chunk_at(self, i);
if c != null { c.used = 0; }
i = i + 1;
}
self.current = 0;
}
fn drop(self: *Arena) {
var i: usize = 0;
while i < self.chunks.len {
var c = chunk_at(self, i);
if c != null { Mem.free(c.base as *any); }
i = i + 1;
}
Vec.drop(&self.chunks);
self.current = 0;
}
fn is_aligned(p: *u8, align: usize): bool {
ret ((p as usize) & (align - 1)) == 0;
}
fn alloc_ok(a: *Arena, size: usize, align: usize): *u8 {
match alloc(a, size, align) {
.Ok(ptr) => { ret ptr; },
.Err(error) => { @assert(false, "unexpected arena alloc error"); },
}
ret null;
}
fn expect_alloc_error(result: ArenaResult, expected: Kind) {
match result {
.Ok(ptr) => { @assert(false, "expected arena alloc error"); },
.Err(error) => {
var want: Kind = expected;
@assert(@enum2int(error.kind) == @enum2int(want), "arena alloc error kind");
},
}
}
fn test_alloc_and_alignment() {
var a = new(64);
@assert(chunk_count(a) == 0, "lazy");
var p1 = alloc_ok(&a, 1, 1);
var p8 = alloc_ok(&a, 16, 8);
var p16 = alloc_ok(&a, 1, 16);
@assert(p1 != null && p8 != null && p16 != null, "alloc");
@assert(p1 != p8 && p8 != p16 && p1 != p16, "distinct");
@assert(is_aligned(p8, 8), "align 8");
@assert(is_aligned(p16, 16), "align 16");
p1[0] = 7;
@assert(p1[0] == 7, "writable");
@assert(p8[0] == p8[0], "no crash");
@assert(chunk_count(a) == 1, "one chunk");
reset(&a);
@assert(chunk_count(a) == 1, "reset keeps chunks");
var q = alloc_ok(&a, 1, 1);
@assert(q == p1, "reset reuses first chunk");
// drop 后还有断言,故保持手动释放,不改为 defer。
drop(&a);
@assert(chunk_count(a) == 0, "drop empties");
}
fn test_growth() {
var a = new(64);
defer { drop(&a); }
var i: i32 = 0;
while i < 100 {
var p = alloc_ok(&a, 16, 8);
@assert(p != null, "grow alloc");
@assert(is_aligned(p, 8), "grow align");
p[0] = i as u8;
i = i + 1;
}
@assert(chunk_count(a) > 1, "grew multiple chunks");
// 超过块大小的单次分配也能成功(新块按需放大)。
var big = alloc_ok(&a, 1024, 16);
@assert(big != null && is_aligned(big, 16), "oversized alloc");
}
fn test_failures() {
var a = new(64);
defer { drop(&a); }
expect_alloc_error(alloc(&a, 0, 1), .InvalidArgument);
var d = alloc_ok(&a, 8, 0);
@assert(d != null && is_aligned(d, 1), "align 0 defaults to 1");
expect_alloc_error(alloc(&a, 8, 3), .InvalidAlignment);
}
fn main() {
test_alloc_and_alignment();
test_growth();
test_failures();
}