212 lines
6.1 KiB
Plaintext
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();
|
|
}
|