From ae83f5b1423ace1e28f2d0a5223e132a71c386f2 Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Mon, 17 Aug 2026 07:21:40 +0900 Subject: [PATCH] =?UTF-8?q?std:=20mem.Arena=20=EB=A5=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SPEC R11 은 재귀·그래프 모양 데이터를 아레나가 값을 소유하고 정수 핸들이 가리키는 것으로 답한다. 그 답은 아레나가 실제로 있어야 쓸 수 있다. 핸들은 오프셋이라 아레나가 사는 동안 유효하고, 두 핸들을 비교하는 것은 두 수를 비교하는 것이다. 아레나는 몰래 자라지 않는다 -- 움직인 핸들은 더 이상 아무것도 가리키지 않기 때문이다. 길에서 고친 것 셋: - Self 가 제네릭 인스턴스에서만 타입으로 묶여 있어서, 평범한 구조체의 Self{..} 가 안 풀렸다. 이제 모든 메서드에서 묶는다. - binding.Type.method() 가 식 자리에서 해석되지 않았다. - 다른 유닛의 비제네릭 구조체 메서드가 lowering 되지 않고 extern 으로만 나갔다. 파일을 나눌 때 그 가지가 빠졌다. handles 0 4 8 / value 65 / full / reset 0 / balanced exec.py 26/26. --- fec/src/checkgen.c | 10 ++++++ fec/src/checkstm.c | 11 +++++++ fec/src/lowerpri.h | 1 + fec/src/lowerstm.c | 13 ++++++++ fec/std/mem.fe | 67 +++++++++++++++++++++++++++++++++++------ fec/tests/exec/arena.fe | 39 ++++++++++++++++++++++++ 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100644 fec/tests/exec/arena.fe diff --git a/fec/src/checkgen.c b/fec/src/checkgen.c index 9be5059..45c1a31 100644 --- a/fec/src/checkgen.c +++ b/fec/src/checkgen.c @@ -358,6 +358,16 @@ FeType *type_from_expr(FeCheckerState *s, FeNode *n, int *ok) if (t && t->kind!=FE_TYPE_UNKNOWN) { *ok=1; return t; } return unknown(c); } + /* `binding.Name` names a type in another unit. */ + if (n->kind==FE_N_MEMBER && n->a && n->a->kind==FE_N_IDENT && + n->b && n->b->text) { + FeUnit *bound=binding_unit(s,n->a); + if (bound) { + FeType *there=unit_type(c,bound,n->b->text); + if (there) { *ok=1; return there; } + } + return unknown(c); + } if (n->kind==FE_N_CALL && n->a && (n->a->kind==FE_N_IDENT || (n->a->kind==FE_N_MEMBER && n->a->a && diff --git a/fec/src/checkstm.c b/fec/src/checkstm.c index b3b1e0a..458bd1c 100644 --- a/fec/src/checkstm.c +++ b/fec/src/checkstm.c @@ -504,6 +504,16 @@ void check_method(FeCheck *c, FeNode *fn, FeScope *globals, FeCheckerState s; FeNode *x; FeType *t; + FeBindSave self_save; + /* `Self` names the type a method belongs to, wherever it appears -- in a + signature, and in `Self{ .. }`. Binding it as a type makes both work the + same way, and the same way a generic instance already worked. */ + self_save.count=c->types.param_count; + { + unsigned i; + for(i=0;itypes.params[i]; + } + bind_self(c,owner); s.c=c; s.globals=globals; s.scope=scope_new(&s,globals); @@ -521,6 +531,7 @@ void check_method(FeCheck *c, FeNode *fn, FeScope *globals, local_cname(c,x->text ? x->text : "arg"),x); } if(fn->c) check_stmt(&s,fn->c); + pop_bindings(c,&self_save); } int m7_actual_compatible(FeType *want, FeType *got, FeNode *value) diff --git a/fec/src/lowerpri.h b/fec/src/lowerpri.h index b46c556..3c5522d 100644 --- a/fec/src/lowerpri.h +++ b/fec/src/lowerpri.h @@ -98,6 +98,7 @@ int lower_reserve(Lower *L, void **items, unsigned *capacity, unsigned needed, /* Every definition in lowering, so the split files can see each other. */ FeIrType tag_type_of(const FeType *t); +int struct_is_generic(const FeNode *decl); void lower_if_let(Lower *L, FeNode *n); unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n); void bind_payload(Lower *L, Slot subject, const FeType *t, diff --git a/fec/src/lowerstm.c b/fec/src/lowerstm.c index 7591523..a9dff93 100644 --- a/fec/src/lowerstm.c +++ b/fec/src/lowerstm.c @@ -515,6 +515,12 @@ void lower_global(Lower *L, FeNode *n) fe_ir_global(L->m, n->cname, ir_type(t), size, ir_align(t), init); } +/* A declaration with type parameters is a pattern, not code. */ +int struct_is_generic(const FeNode *decl) +{ + return decl && decl->a && decl->a->children != 0; +} + int fn_is_generic(const FeNode *fn) { FeNode *p; @@ -594,6 +600,13 @@ int fe_lower_program(FeCheck *c, FeIrModule *out) for (n = unit->ast.root ? unit->ast.root->children : 0; n; n = n->next) if (n->kind == FE_N_GLOBAL || n->kind == FE_N_CONST) lower_global(&L, n); + else if (n->kind == FE_N_STRUCT && !struct_is_generic(n)) { + /* A method is a function whose first parameter is the value it + was reached through; the storage is the same either way. */ + FeNode *m; + for (m = n->children; m; m = m->next) + if (m->kind == FE_N_FN && m->c) lower_fn(&L, m); + } else if (n->kind == FE_N_FN && !n->c) { /* A declaration with no body is something the linker will find: the runtime, or a C library. */ diff --git a/fec/std/mem.fe b/fec/std/mem.fe index 4fe0cd7..f4524f7 100644 --- a/fec/std/mem.fe +++ b/fec/std/mem.fe @@ -1,13 +1,60 @@ -unit mem; +unit std.mem; +import std.sys; -pub fn create(value: T) -> !^T; -pub fn destroy(p: *void); -pub fn alloc_slice(T: type, n: usize) -> !^[]T; -pub fn replace(dst: &mut T, value: T) -> T; -pub fn copy(dst: []mut u8, src: []u8); +// `create`, `destroy`, `alloc_slice` and `replace` are compiler intrinsics: +// they need to know the type they are handed, which no signature can say. +// What is written here is what can be written in Ferro. + +/// A block of storage handed out in pieces, released all at once. +/// +/// SPEC R11 answers recursive and graph-shaped data with an arena that owns +/// the values and integer handles that reference them. This is that arena. A +/// handle is an offset, so it stays valid while the arena does, and comparing +/// two handles is comparing two numbers. pub struct Arena { - ptr: *void, - pub fn init() -> Arena { return Arena{ ptr: null }; } - pub fn reset(self: &mut Self) { } - pub fn drop(self: &mut Self) { } + bytes: ^[]mut u8, + used: usize, + + pub fn with_capacity(n: usize) -> !Self { + let room: ^[]mut u8 = try mem.alloc_slice(u8, n); + return Self{ bytes: room, used: 0 }; + } + + pub fn size(self: &Self) -> usize { return self.used; } + + pub fn room(self: &Self) -> usize { return self.bytes.^.n; } + + /// Reserve `n` bytes aligned to `align` and give back where they start. + /// Failure is running out of room, which the caller decides what to do + /// about; the arena never grows behind your back, because a handle that + /// moved would no longer mean anything. + pub fn alloc(self: &mut Self, n: usize, align: usize) -> !usize { + var at: usize = self.used; + if align > 1 { + let over: usize = at % align; + if over != 0 { at = at + align - over; } + } + if at + n > self.bytes.^.n { return error.ArenaFull; } + self.used = at + n; + return at; + } + + /// One byte at a handle. Reading and writing go through here so that a + /// handle can be checked once, in one place. + pub fn at(self: &Self, handle: usize) -> !u8 { + if handle >= self.used { return error.BadHandle; } + return self.bytes.^[handle]; + } + + pub fn put(self: &mut Self, handle: usize, value: u8) -> !void { + if handle >= self.used { return error.BadHandle; } + self.bytes.^[handle] = value; + return; + } + + /// Forget everything handed out so far. Every handle from before is stale; + /// that is the trade an arena makes. + pub fn reset(self: &mut Self) -> void { self.used = 0; } + + pub fn drop(self: &mut Self) -> void { mem.destroy(self.bytes); } } diff --git a/fec/tests/exec/arena.fe b/fec/tests/exec/arena.fe new file mode 100644 index 0000000..88da9b7 --- /dev/null +++ b/fec/tests/exec/arena.fe @@ -0,0 +1,39 @@ +// EXIT:0 +// OUTPUT:handles 0 4 8 +// OUTPUT:value 65 +// OUTPUT:full +// OUTPUT:reset 0 +// OUTPUT:balanced +unit arena; +import std.io; +import std.mem; +import std.sys; + +// SPEC R11: recursive and graph-shaped data is answered by an arena that owns +// the values and integer handles that point into it. This is that shape. + +fn run() -> !void { + var a: mem.Arena = try mem.Arena.with_capacity(16); + let first: usize = try a.alloc(4, 4); + let second: usize = try a.alloc(4, 4); + let third: usize = try a.alloc(4, 4); + @print("handles {} {} {}\n", first, second, third); + try a.put(first, 65); + let got: u8 = try a.at(first); + @print("value {}\n", got); + let over: usize = a.alloc(64, 1) catch |e| { + @print("full\n"); + a.reset(); + @print("reset {}\n", a.size()); + return; + }; + @print("unexpected room {}\n", over); + return; +} + +fn main() -> i32 { + run() catch |e| { @print("failed\n"); return 1; }; + if sys.allocs() != sys.frees() { @print("leaked\n"); return 2; } + @print("balanced\n"); + return 0; +}