lower: 제네릭 인스턴스와 메서드

모노모피제이션이 실제로 코드를 만드는 자리가 여기다. 프론트엔드는 어떤
인스턴스가 존재하는지만 정했다. 검사기가 인스턴스마다 선언·바인딩·유닛·링크
이름을 기록하고, lowering 이 그 바인딩을 다시 걸고 같은 본문을 자기 이름으로
내린다. 제네릭 선언 자체는 코드가 없다.

comptime 인자는 값이 아니므로 호출에서 넘기지 않고 파라미터 자리도 잡지
않는다. 메서드 호출은 도달한 대상을 첫 인자로 넘긴다 -- self: Self 든
self: &Self 든 수신자의 주소로 같다. 구조체 메서드가 아예 lowering 되지
않고 있었다.

exec.py 13/13.
This commit is contained in:
2026-08-17 06:26:20 +09:00
parent eba6f63530
commit 231c7d564b
4 changed files with 157 additions and 19 deletions
+46 -10
View File
@@ -464,12 +464,14 @@ static void pop_bindings(FeCheck *c, const FeBindSave *save);
static void bind_self(FeCheck *c, FeType *owner);
static void instance_key(char *out, const char *unit, const char *name,
FeType **args, unsigned count);
static int instance_record(FeCheck *c, const char *key, FeLoc loc);
static int instance_record(FeCheck *c, const char *key, FeLoc loc,
FeNode *decl, FeUnit *home, FeType *owner);
static const char *instance_cname(FeCheck *c, const char *key);
static int instance_descend(FeCheck *c, FeLoc loc);
static void instantiate_body(FeCheck *c, FeUnit *home, FeNode *decl,
FeType *owner, FeBindSave *bindings, FeLoc site);
static void check_instance_method(FeCheckerState *s, FeType *owner,
FeNode *method, FeLoc site);
FeNode *method, FeLoc site, FeNode *call);
typedef struct FeFlowSlot {
FeSym *sym;
@@ -1318,7 +1320,7 @@ static FeType *check_expr_core(FeCheckerState *s, FeNode *n)
fe_type_intern(&c->types,"void");
if (bound) {
pop_bindings(c,&msave);
check_instance_method(s,et,method,n->loc);
check_instance_method(s,et,method,n->loc,n);
}
return n->sem_type;
}
@@ -2249,6 +2251,14 @@ static void instance_key(char *out, const char *unit, const char *name,
/* Already built, or being built right now. Re-asking for a pending instance is
how a recursive generic terminates, so it must not look like a new one. */
static const char *instance_cname(FeCheck *c, const char *key)
{
unsigned i;
for (i=0;i<c->instance_count;++i)
if (!strcmp(c->instances[i].key,key)) return c->instances[i].cname;
return 0;
}
static int instance_known(FeCheck *c, const char *key)
{
unsigned i;
@@ -2257,14 +2267,27 @@ static int instance_known(FeCheck *c, const char *key)
return 0;
}
static int instance_record(FeCheck *c, const char *key, FeLoc loc)
static int instance_record(FeCheck *c, const char *key, FeLoc loc,
FeNode *decl, FeUnit *home, FeType *owner)
{
FeInstance *inst;
unsigned i;
if (instance_known(c,key)) return 0;
if (c->instance_count>=FE_GENERIC_INSTANCE_MAX) {
err(c,loc,"too many generic instances");
return -1;
}
strcpy(c->instances[c->instance_count].key,key);
inst=&c->instances[c->instance_count];
strcpy(inst->key,key);
inst->decl=decl;
inst->home=home ? home->name : 0;
inst->owner=owner;
inst->cname=unit_cname(c,key);
/* The bindings in force right now are the ones this instance was built
with, and lowering has to see exactly those again. */
inst->bind_count=c->types.param_count;
for (i=0;i<c->types.param_count && i<FE_TYPE_PARAM_MAX;++i)
inst->binds[i]=c->types.params[i];
++c->instance_count;
return 1;
}
@@ -2357,7 +2380,7 @@ static FeType *instantiate_struct(FeCheck *c, FeUnit *home, const char *name,
return unknown(c);
}
instance_key(key,home->name,name,args,count);
if (instance_record(c,key,loc)<0) return unknown(c);
if (instance_record(c,key,loc,decl,home,0)<0) return unknown(c);
return build_struct_instance(c,home,decl,key,args,count);
}
@@ -2537,8 +2560,10 @@ static FeType *check_generic_call(FeCheckerState *s, FeNode *n, FeSym *sym,
instance_key(key,home->name,decl->text,args,want);
push_bindings(c,&save,decl,args,want);
result=check_call_args(s,n,sym,home->name,want);
fresh=instance_record(c,key,n->loc,decl,home,0);
pop_bindings(c,&save);
fresh=instance_record(c,key,n->loc);
/* The call goes to this instance, not to the declaration it came from. */
if (n->a) n->a->cname=(char *)instance_cname(c,key);
if (fresh>0) {
if (!instance_descend(c,n->loc)) return result;
push_bindings(c,&save,decl,args,want);
@@ -2572,8 +2597,9 @@ static FeType *check_static_method_call(FeCheckerState *s, FeNode *n,
push_instance_bindings(c,&save,owner);
bind_self(c,owner);
result=check_call_args(s,n,&fake,home->name,0);
fresh=instance_record(c,key,n->loc,method,home,owner);
pop_bindings(c,&save);
fresh=instance_record(c,key,n->loc);
if (n->a) n->a->cname=(char *)instance_cname(c,key);
if (fresh>0) {
if (!instance_descend(c,n->loc)) return result;
push_instance_bindings(c,&save,owner);
@@ -2587,7 +2613,7 @@ static FeType *check_static_method_call(FeCheckerState *s, FeNode *n,
/* The body of a method on a generic instance, checked once per instance. */
static void check_instance_method(FeCheckerState *s, FeType *owner,
FeNode *method, FeLoc site)
FeNode *method, FeLoc site, FeNode *call)
{
FeCheck *c=s->c;
FeUnit *home=current_unit(c);
@@ -2596,7 +2622,17 @@ static void check_instance_method(FeCheckerState *s, FeType *owner,
FeType *self_args[1];
self_args[0]=owner;
instance_key(key,home->name,method->text,self_args,1);
if (instance_record(c,key,site)<=0) return;
{
FeBindSave probe;
int fresh;
push_instance_bindings(c,&probe,owner);
bind_self(c,owner);
fresh=instance_record(c,key,site,method,home,owner);
pop_bindings(c,&probe);
/* The call names this instance's copy of the method. */
if (call && call->a) call->a->cname=(char *)instance_cname(c,key);
if (fresh<=0) return;
}
if (!instance_descend(c,site)) return;
push_instance_bindings(c,&save,owner);
bind_self(c,owner);
+9
View File
@@ -14,6 +14,15 @@ typedef struct FeScope FeScope;
#define FE_GENERIC_INSTANCE_MAX 512
typedef struct FeInstance {
char key[FE_GENERIC_KEY_MAX];
/* What lowering needs to build this instance's code: the declaration, the
arguments bound while it was checked, the unit those names belong to,
and the name the linker will see. */
FeNode *decl;
FeTypeBind binds[FE_TYPE_PARAM_MAX];
unsigned bind_count;
const char *home;
const char *cname;
FeType *owner; /* set when the instance is a method */
} FeInstance;
/* The checker spans a whole build, not one file. Names cross unit boundaries,
+81 -9
View File
@@ -62,6 +62,8 @@ static void lower_stmt(Lower *L, FeNode *n);
static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n,
unsigned long size);
static void lower_for(Lower *L, FeNode *n);
static int fn_is_generic(const FeNode *fn);
static void lower_fn_as(Lower *L, FeNode *fn, const char *name);
static Slot lower_slice(Lower *L, FeNode *n);
static void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line);
static void lower_match(Lower *L, FeNode *n);
@@ -450,7 +452,7 @@ static Slot lower_call(Lower *L, FeNode *n)
{
unsigned args[16];
unsigned count = 0;
FeNode *arg;
FeNode *arg = n->children;
FeType *ret = n->sem_type;
FeIrType rt = ir_type(ret);
unsigned result_local = 0;
@@ -468,7 +470,28 @@ static Slot lower_call(Lower *L, FeNode *n)
ir_align(ret), "result");
args[count++] = fe_ir_addr(L->m, L->b, fe_ir_at_local(result_local, 0));
}
for (arg = n->children; arg; arg = arg->next) {
/* A method call passes what it was reached through as its first argument.
`self: Self` and `self: &Self` are the same thing here: the address of
the receiver, because an aggregate never travels in a register. */
if (n->a && n->a->kind == FE_N_MEMBER && n->sem_decl) {
FeNode *first = n->sem_decl->a ? n->sem_decl->a->children : 0;
if (first && first->text && !strcmp(first->text, "self")) {
Slot recv = lower_expr(L, n->a->a);
args[count++] = recv.is_place ? as_address(L, recv, n->a->a)
: recv.temp;
}
}
/* A generic call passes its type arguments first. They were consumed when
the instance was chosen and carry no value, so they are not passed. */
{
FeNode *p;
for (p = n->sem_decl && n->sem_decl->a ? n->sem_decl->a->children : 0;
p && arg; p = p->next) {
if (!(p->flags & FE_NODE_COMPTIME)) break;
arg = arg->next;
}
}
for (; arg; arg = arg->next) {
Slot a = lower_expr(L, arg);
if (count >= 16) { fail(L, "too many arguments", n); break; }
args[count++] = a.type == FE_IR_MEM ? as_address(L, a, arg)
@@ -1286,13 +1309,22 @@ static void lower_global(Lower *L, FeNode *n)
fe_ir_global(L->m, n->cname, ir_type(t), size, ir_align(t), init);
}
static void lower_fn(Lower *L, FeNode *fn)
static int fn_is_generic(const FeNode *fn)
{
FeNode *p;
if (!fn) return 0;
for (p = fn->a ? fn->a->children : 0; p; p = p->next)
if (p->flags & FE_NODE_COMPTIME) return 1;
return 0;
}
static void lower_fn_as(Lower *L, FeNode *fn, const char *name)
{
FeNode *p;
FeType *ret = fn->b ? fe_type_from_ast(&L->c->types, fn->b) : 0;
FeIrFunc *f;
if (!fn->cname) return;
f = fe_ir_func(L->m, fn->cname, ir_type(ret), ir_size(ret));
if (!name) return;
f = fe_ir_func(L->m, name, ir_type(ret), ir_size(ret));
if (!f) return;
L->fn = f;
L->ret_type = ret;
@@ -1302,10 +1334,16 @@ static void lower_fn(Lower *L, FeNode *fn)
if (f->returns_by_address)
L->ret_local = fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, "result");
for (p = fn->a ? fn->a->children : 0; p; p = p->next) {
FeType *pt = fe_type_from_ast(&L->c->types, p->a);
FeType *pt;
int by_address;
unsigned local;
/* A comptime parameter was consumed at compile time; it has no
storage and takes no argument slot. */
if (p->flags & FE_NODE_COMPTIME) continue;
pt = fe_type_from_ast(&L->c->types, p->a);
/* An aggregate parameter arrives as an address. */
int by_address = ir_type(pt) == FE_IR_MEM;
unsigned local = by_address
by_address = ir_type(pt) == FE_IR_MEM;
local = by_address
? fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, p->text)
: fe_ir_local(L->m, f, ir_type(pt), ir_size(pt), ir_align(pt),
p->text);
@@ -1323,6 +1361,11 @@ static void lower_fn(Lower *L, FeNode *fn)
fe_ir_ret(L->b, 0, 0);
}
static void lower_fn(Lower *L, FeNode *fn)
{
lower_fn_as(L, fn, fn->cname);
}
int fe_lower_program(FeCheck *c, FeIrModule *out)
{
Lower L;
@@ -1353,12 +1396,41 @@ int fe_lower_program(FeCheck *c, FeIrModule *out)
f = fe_ir_func(out, n->cname, ir_type(ret), ir_size(ret));
if (f) f->is_extern = 1;
}
else if (n->kind == FE_N_FN && n->c) {
else if (n->kind == FE_N_FN && n->c && !fn_is_generic(n)) {
lower_fn(&L, n);
/* The entry unit is the one the build was rooted at. */
if (u == 0 && n->text && !strcmp(n->text, "main"))
out->entry_main = n->cname;
}
}
/* Each instance the checker reached is a function of its own: the same
body, read with different types bound, under its own link name. This is
where monomorphisation actually produces code -- the front end only
decided which instances exist. */
for (u = 0; u < c->instance_count && !L.failed; ++u) {
FeInstance *inst = &c->instances[u];
FeUnit *home;
FeTypeBind save[FE_TYPE_PARAM_MAX];
unsigned save_count;
unsigned k;
if (!inst->decl || !inst->decl->c || !inst->cname || !inst->home)
continue;
home = 0;
for (k = 0; k < c->build->count; ++k)
if (!strcmp(c->build->units[k].name, inst->home))
home = &c->build->units[k];
if (!home) continue;
c->ast = &home->ast;
c->unit = home;
c->types.unit_name = home->name;
save_count = c->types.param_count;
for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) save[k] = c->types.params[k];
c->types.param_count = inst->bind_count;
for (k = 0; k < inst->bind_count && k < FE_TYPE_PARAM_MAX; ++k)
c->types.params[k] = inst->binds[k];
lower_fn_as(&L, inst->decl, inst->cname);
c->types.param_count = save_count;
for (k = 0; k < FE_TYPE_PARAM_MAX; ++k) c->types.params[k] = save[k];
}
return !L.failed;
}
+21
View File
@@ -0,0 +1,21 @@
// EXIT:39
unit generic;
struct Box(T) {
value: T,
pub fn new(v: T) -> Self { return Self{ value: v }; }
pub fn get(self: &Self) -> T { return self.value; }
}
fn id(comptime T: type, v: T) -> T { return v; }
fn twice(comptime T: type, v: T) -> T { return v + v; }
fn main() -> i32 {
let a: i32 = id(i32, 7);
let b: u8 = id(u8, 9 as u8);
let c: i32 = twice(i32, 10);
let box: Box(i32) = Box(i32).new(3);
return a + (b as i32) + c + box.get();
}