compiler: 조용히 잘리던 상한을 없앤다

lowering 의 변수·정리 목록·오류 이름 세 배열은 넘치면 오류가 아니라 넘친 것을
버리고 틀린 코드를 냈다. 한계에 닿는 방식 중 최악이다. 이제 자란다.

코드 생성기는 지역이 512개를 넘으면 함수를 아예 방출하지 않고 지나갔다. 이제
지역 수만큼 자리를 잡는다.

유닛 64 -> 256, 제네릭 인스턴스 512 -> 4096. 둘 다 원래 보고는 했지만 장난감을
기준으로 고른 숫자였다.

209 -> 213 fixture, exec 25/25.
This commit is contained in:
2026-08-17 07:18:47 +09:00
parent 0fd3f187f4
commit c06f5c50c4
6 changed files with 65 additions and 16 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ typedef struct FeScope FeScope;
spelling of its type arguments (SPEC 9). The table both deduplicates spelling of its type arguments (SPEC 9). The table both deduplicates
requests and bounds how long a chain of new ones can get. */ requests and bounds how long a chain of new ones can get. */
#define FE_GENERIC_KEY_MAX 320 #define FE_GENERIC_KEY_MAX 320
#define FE_GENERIC_INSTANCE_MAX 512 #define FE_GENERIC_INSTANCE_MAX 4096
typedef struct FeInstance { typedef struct FeInstance {
char key[FE_GENERIC_KEY_MAX]; char key[FE_GENERIC_KEY_MAX];
/* What lowering needs to build this instance's code: the declaration, the /* What lowering needs to build this instance's code: the declaration, the
+25 -3
View File
@@ -118,18 +118,37 @@ int needs_release(const FeType *t)
return t->has_drop != 0; return t->has_drop != 0;
} }
int lower_reserve(Lower *L, void **items, unsigned *capacity, unsigned needed,
unsigned long item_size)
{
unsigned want;
void *grown;
if (needed < *capacity) return 1;
want = *capacity ? *capacity * 2U : 16U;
while (want <= needed) want *= 2U;
grown = fe_arena_alloc(&L->m->arena, (size_t)(want * item_size));
if (!grown) { fail(L, "a function this large", 0); return 0; }
if (*items) memcpy(grown, *items, (size_t)(*capacity * item_size));
*items = grown;
*capacity = want;
return 1;
}
unsigned declare_var(Lower *L, const char *cname, const FeType *t, unsigned declare_var(Lower *L, const char *cname, const FeType *t,
const char *name) const char *name)
{ {
unsigned local = fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t), unsigned local = fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t),
ir_align(t), name); ir_align(t), name);
if (L->var_count < LOWER_MAX_LOCALS) { if (lower_reserve(L, (void **)&L->vars, &L->var_capacity, L->var_count,
(unsigned long)sizeof(LowerVar))) {
L->vars[L->var_count].cname = cname; L->vars[L->var_count].cname = cname;
L->vars[L->var_count].local = local; L->vars[L->var_count].local = local;
L->vars[L->var_count].by_address = 0; L->vars[L->var_count].by_address = 0;
++L->var_count; ++L->var_count;
} }
if (needs_release(t) && L->owed_count < 64) { if (needs_release(t) &&
lower_reserve(L, (void **)&L->owed, &L->owed_capacity, L->owed_count,
(unsigned long)sizeof *L->owed)) {
unsigned flag = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "live"); unsigned flag = fe_ir_local(L->m, L->fn, FE_IR_I8, 1, 1, "live");
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0); unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0);
fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), zero, FE_IR_I8); fe_ir_store(L->m, L->b, fe_ir_at_local(flag, 0), zero, FE_IR_I8);
@@ -230,7 +249,10 @@ void note_error_name(Lower *L, const char *name)
{ {
unsigned i; unsigned i;
unsigned at; unsigned at;
if (!name || L->error_count >= 256) return; if (!name) return;
if (!lower_reserve(L, (void **)&L->error_names, &L->error_capacity,
L->error_count, (unsigned long)sizeof(const char *)))
return;
for (i = 0; i < L->error_count; ++i) for (i = 0; i < L->error_count; ++i)
if (!strcmp(L->error_names[i], name)) return; if (!strcmp(L->error_names[i], name)) return;
/* Kept sorted as it is built, so the numbering is the spelling order. */ /* Kept sorted as it is built, so the numbering is the spelling order. */
+14 -4
View File
@@ -24,7 +24,6 @@
* register and an aggregate does not fit in one. * register and an aggregate does not fit in one.
* ------------------------------------------------------------------------- */ * ------------------------------------------------------------------------- */
#define LOWER_MAX_LOCALS 256
typedef struct LowerVar { typedef struct LowerVar {
const char *cname; const char *cname;
@@ -41,8 +40,12 @@ typedef struct Lower {
FeIrBlock *b; /* the block being appended to */ FeIrBlock *b; /* the block being appended to */
FeType *ret_type; FeType *ret_type;
unsigned ret_local; /* hidden result address, when returning mem */ unsigned ret_local; /* hidden result address, when returning mem */
LowerVar vars[LOWER_MAX_LOCALS]; /* These three grow. A fixed size here does not report a program that is
too big -- it quietly drops what does not fit and generates wrong code,
which is the worst way for a limit to be reached. */
LowerVar *vars;
unsigned var_count; unsigned var_count;
unsigned var_capacity;
/* Loop targets, for break and continue. */ /* Loop targets, for break and continue. */
unsigned break_target[32]; unsigned break_target[32];
unsigned continue_target[32]; unsigned continue_target[32];
@@ -60,14 +63,16 @@ typedef struct Lower {
unsigned local; /* the owned value, otherwise */ unsigned local; /* the owned value, otherwise */
unsigned flag; unsigned flag;
FeType *type; FeType *type;
} owed[64]; } *owed;
unsigned owed_count; unsigned owed_count;
unsigned owed_capacity;
/* Every `error.Name` used anywhere in the build, sorted, numbered from one. /* Every `error.Name` used anywhere in the build, sorted, numbered from one.
SPEC 4.6: the names are collected rather than declared, and the order is SPEC 4.6: the names are collected rather than declared, and the order is
fixed by the spelling so that the same program always gets the same fixed by the spelling so that the same program always gets the same
codes however the build was ordered. */ codes however the build was ordered. */
const char *error_names[256]; const char **error_names;
unsigned error_count; unsigned error_count;
unsigned error_capacity;
int failed; int failed;
} Lower; } Lower;
@@ -86,6 +91,11 @@ typedef struct Slot {
#define SLICE_PTR_OFFSET 0L #define SLICE_PTR_OFFSET 0L
#define SLICE_LEN_OFFSET 4L #define SLICE_LEN_OFFSET 4L
/* Grow one of the checker's own arrays. Returns zero when there is no more
memory, which the caller reports rather than ignores. */
int lower_reserve(Lower *L, void **items, unsigned *capacity, unsigned needed,
unsigned long item_size);
/* Every definition in lowering, so the split files can see each other. */ /* Every definition in lowering, so the split files can see each other. */
FeIrType tag_type_of(const FeType *t); FeIrType tag_type_of(const FeType *t);
void lower_if_let(Lower *L, FeNode *n); void lower_if_let(Lower *L, FeNode *n);
+16 -5
View File
@@ -198,21 +198,30 @@ void lower_for(Lower *L, FeNode *n)
with one it is the element. */ with one it is the element. */
counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index"); counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index");
if (n->aux_cname) { if (n->aux_cname) {
(void)lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count,
(unsigned long)sizeof(LowerVar));
L->vars[L->var_count].cname = n->cname; L->vars[L->var_count].cname = n->cname;
L->vars[L->var_count].local = counter; L->vars[L->var_count].local = counter;
L->vars[L->var_count].by_address = 0; L->vars[L->var_count].by_address = 0;
if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; ++L->var_count;
item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->aux_text); item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->aux_text);
(void)lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count,
(unsigned long)sizeof(LowerVar));
L->vars[L->var_count].cname = n->aux_cname; L->vars[L->var_count].cname = n->aux_cname;
L->vars[L->var_count].local = item; L->vars[L->var_count].local = item;
L->vars[L->var_count].by_address = 0; L->vars[L->var_count].by_address = 0;
if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; ++L->var_count;
} else { } else {
item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->text); item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->text);
(void)lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count,
(unsigned long)sizeof(LowerVar));
L->vars[L->var_count].cname = n->cname; L->vars[L->var_count].cname = n->cname;
L->vars[L->var_count].local = item; L->vars[L->var_count].local = item;
L->vars[L->var_count].by_address = 0; L->vars[L->var_count].by_address = 0;
if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count; ++L->var_count;
} }
{ {
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I32, 0); unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I32, 0);
@@ -463,7 +472,8 @@ void lower_stmt(Lower *L, FeNode *n)
lower_stmt(L, n->a); lower_stmt(L, n->a);
return; return;
case FE_N_DEFER: case FE_N_DEFER:
if (L->owed_count < 64) { if (lower_reserve(L, (void **)&L->owed, &L->owed_capacity,
L->owed_count, (unsigned long)sizeof *L->owed)) {
L->owed[L->owed_count].block = n->a; L->owed[L->owed_count].block = n->a;
L->owed[L->owed_count].local = 0; L->owed[L->owed_count].local = 0;
L->owed[L->owed_count].flag = 0; L->owed[L->owed_count].flag = 0;
@@ -543,7 +553,8 @@ void lower_fn_as(Lower *L, FeNode *fn, const char *name)
? fe_ir_local(L->m, f, FE_IR_PTR, 4, 4, p->text) ? 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), : fe_ir_local(L->m, f, ir_type(pt), ir_size(pt), ir_align(pt),
p->text); p->text);
if (L->var_count < LOWER_MAX_LOCALS) { if (lower_reserve(L, (void **)&L->vars, &L->var_capacity,
L->var_count, (unsigned long)sizeof(LowerVar))) {
L->vars[L->var_count].cname = p->cname; L->vars[L->var_count].cname = p->cname;
L->vars[L->var_count].local = local; L->vars[L->var_count].local = local;
L->vars[L->var_count].by_address = by_address; L->vars[L->var_count].by_address = by_address;
+1 -1
View File
@@ -15,7 +15,7 @@
what makes a unit path map to a FAT/DOS 8.3 source path unambiguously. */ what makes a unit path map to a FAT/DOS 8.3 source path unambiguously. */
#define FE_UNIT_SEGMENT_MAX 8 #define FE_UNIT_SEGMENT_MAX 8
#define FE_UNIT_PATH_MAX 128 #define FE_UNIT_PATH_MAX 128
#define FE_BUILD_UNIT_MAX 64 #define FE_BUILD_UNIT_MAX 256
typedef struct FeUnit { typedef struct FeUnit {
char name[FE_UNIT_PATH_MAX]; /* canonical dotted path */ char name[FE_UNIT_PATH_MAX]; /* canonical dotted path */
+8 -2
View File
@@ -1,5 +1,6 @@
#include "x86.h" #include "x86.h"
#include <string.h> #include <string.h>
#include <stdlib.h>
/* ------------------------------------------------------------------------- * /* ------------------------------------------------------------------------- *
* i386 code generation * i386 code generation
@@ -269,13 +270,17 @@ static void emit_value(const Frame *fr, const FeIrValue *v, FILE *out)
static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out) static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out)
{ {
Frame fr; Frame fr;
long storage[512]; long *storage;
const FeIrBlock *b; const FeIrBlock *b;
const FeIrValue *v; const FeIrValue *v;
unsigned i; unsigned i;
long arg = 8; long arg = 8;
if (f->is_extern || !f->first) return; if (f->is_extern || !f->first) return;
if (f->local_count > 512) return; /* One offset per local, however many there are. A fixed array here would
silently stop emitting a function that had too many. */
storage = (long *)malloc((size_t)(f->local_count ? f->local_count : 1) *
sizeof(long));
if (!storage) return;
frame_layout(&fr, f, storage); frame_layout(&fr, f, storage);
fprintf(out, "\npublic %s\n", f->name); fprintf(out, "\npublic %s\n", f->name);
@@ -322,6 +327,7 @@ static void emit_func(const FeIrModule *m, const FeIrFunc *f, FILE *out)
} }
} }
fprintf(out, "%s endp\n", f->name); fprintf(out, "%s endp\n", f->name);
free(storage);
(void)m; (void)m;
} }