std: mem.Arena 를 구현한다

SPEC R11 은 재귀·그래프 모양 데이터를 아레나가 값을 소유하고 정수 핸들이
가리키는 것으로 답한다. 그 답은 아레나가 실제로 있어야 쓸 수 있다.

핸들은 오프셋이라 아레나가 사는 동안 유효하고, 두 핸들을 비교하는 것은 두 수를
비교하는 것이다. 아레나는 몰래 자라지 않는다 -- 움직인 핸들은 더 이상 아무것도
가리키지 않기 때문이다.

길에서 고친 것 셋:

- Self 가 제네릭 인스턴스에서만 타입으로 묶여 있어서, 평범한 구조체의 Self{..}
  가 안 풀렸다. 이제 모든 메서드에서 묶는다.
- binding.Type.method() 가 식 자리에서 해석되지 않았다.
- 다른 유닛의 비제네릭 구조체 메서드가 lowering 되지 않고 extern 으로만 나갔다.
  파일을 나눌 때 그 가지가 빠졌다.

  handles 0 4 8 / value 65 / full / reset 0 / balanced

exec.py 26/26.
This commit is contained in:
2026-08-17 07:21:40 +09:00
parent c06f5c50c4
commit ae83f5b142
6 changed files with 131 additions and 10 deletions
+10
View File
@@ -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 &&
+11
View File
@@ -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;i<FE_TYPE_PARAM_MAX;++i) self_save.params[i]=c->types.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)
+1
View File
@@ -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,
+13
View File
@@ -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. */
+57 -10
View File
@@ -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); }
}
+39
View File
@@ -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;
}