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
requests and bounds how long a chain of new ones can get. */
#define FE_GENERIC_KEY_MAX 320
#define FE_GENERIC_INSTANCE_MAX 512
#define FE_GENERIC_INSTANCE_MAX 4096
typedef struct FeInstance {
char key[FE_GENERIC_KEY_MAX];
/* 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;
}
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,
const char *name)
{
unsigned local = fe_ir_local(L->m, L->fn, ir_type(t), ir_size(t),
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].local = local;
L->vars[L->var_count].by_address = 0;
++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 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);
@@ -230,7 +249,10 @@ void note_error_name(Lower *L, const char *name)
{
unsigned i;
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)
if (!strcmp(L->error_names[i], name)) return;
/* 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.
* ------------------------------------------------------------------------- */
#define LOWER_MAX_LOCALS 256
typedef struct LowerVar {
const char *cname;
@@ -41,8 +40,12 @@ typedef struct Lower {
FeIrBlock *b; /* the block being appended to */
FeType *ret_type;
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_capacity;
/* Loop targets, for break and continue. */
unsigned break_target[32];
unsigned continue_target[32];
@@ -60,14 +63,16 @@ typedef struct Lower {
unsigned local; /* the owned value, otherwise */
unsigned flag;
FeType *type;
} owed[64];
} *owed;
unsigned owed_count;
unsigned owed_capacity;
/* 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
fixed by the spelling so that the same program always gets the same
codes however the build was ordered. */
const char *error_names[256];
const char **error_names;
unsigned error_count;
unsigned error_capacity;
int failed;
} Lower;
@@ -86,6 +91,11 @@ typedef struct Slot {
#define SLICE_PTR_OFFSET 0L
#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. */
FeIrType tag_type_of(const FeType *t);
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. */
counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index");
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].local = counter;
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);
(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].local = item;
L->vars[L->var_count].by_address = 0;
if (L->var_count < LOWER_MAX_LOCALS) ++L->var_count;
++L->var_count;
} else {
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].local = item;
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);
@@ -463,7 +472,8 @@ void lower_stmt(Lower *L, FeNode *n)
lower_stmt(L, n->a);
return;
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].local = 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, ir_type(pt), ir_size(pt), ir_align(pt),
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].local = local;
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. */
#define FE_UNIT_SEGMENT_MAX 8
#define FE_UNIT_PATH_MAX 128
#define FE_BUILD_UNIT_MAX 64
#define FE_BUILD_UNIT_MAX 256
typedef struct FeUnit {
char name[FE_UNIT_PATH_MAX]; /* canonical dotted path */
+8 -2
View File
@@ -1,5 +1,6 @@
#include "x86.h"
#include <string.h>
#include <stdlib.h>
/* ------------------------------------------------------------------------- *
* 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)
{
Frame fr;
long storage[512];
long *storage;
const FeIrBlock *b;
const FeIrValue *v;
unsigned i;
long arg = 8;
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);
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);
free(storage);
(void)m;
}