From 096a5db4112457c60efb5b77866c295c12414ebe Mon Sep 17 00:00:00 2001 From: Sebastian Jeong Date: Mon, 17 Aug 2026 06:06:35 +0900 Subject: [PATCH] =?UTF-8?q?lower:=20=EC=98=B5=EC=85=94=EB=84=90,=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=9C=A0=EB=8B=88=EC=98=A8,=20try/catch/o?= =?UTF-8?q?relse,=20defer,=20for?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 옵셔널은 태그와 페이로드, 에러 유니온은 오류 코드와 페이로드다. 코드 0 이 '오류 없음'이다. 페이로드 위치 규칙을 types.c 로 옮겨서 레이아웃 패스와 코드 생성기가 같은 것을 본다. try 는 분기다. 실패면 지금 함수의 에러 유니온을 그 코드로 만들어 나간다 -- 그 전에 defer 를 돌린다. catch 와 orelse 는 오른쪽을 필요할 때만 평가하므로 역시 분기다. for 는 세 형태를 공유한다: 세는 것, 원소를 도는 것, 위치까지 받는 것. 개수는 본문 전에 한 번 읽는다. 원소 바인딩은 참조다 -- 그래서 루프가 원본에 쓸 수 있다. error.Name 은 빌드 전체에서 이름을 모아 철자 순으로 1부터 번호를 준다 (SPEC 4.6). 빌드 순서가 결과를 바꾸지 않는다. run.py 197/197, exec.py 9/9. --- fec/src/lower.c | 425 ++++++++++++++++++++++++++++++++++++- fec/src/types.c | 14 ++ fec/src/types.h | 4 + fec/tests/exec/errunion.fe | 18 ++ fec/tests/exec/forloop.fe | 11 + fec/tests/exec/optional.fe | 15 ++ 6 files changed, 485 insertions(+), 2 deletions(-) create mode 100644 fec/tests/exec/errunion.fe create mode 100644 fec/tests/exec/forloop.fe create mode 100644 fec/tests/exec/optional.fe diff --git a/fec/src/lower.c b/fec/src/lower.c index 4bc3d6a..aec95a5 100644 --- a/fec/src/lower.c +++ b/fec/src/lower.c @@ -1,5 +1,6 @@ #include "lower.h" #include +#include "m7.h" #include /* ------------------------------------------------------------------------- * @@ -35,6 +36,16 @@ typedef struct Lower { unsigned break_target[32]; unsigned continue_target[32]; unsigned loop_depth; + /* `defer` blocks in the order they were written. Every exit path runs the + ones that are live, last written first. */ + FeNode *deferred[32]; + unsigned defer_count; + /* 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]; + unsigned error_count; int failed; } Lower; @@ -50,6 +61,19 @@ static Slot lower_expr(Lower *L, FeNode *n); 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 Slot wrap_context(Lower *L, Slot v, FeNode *n); +static Slot lower_try(Lower *L, FeNode *n); +static Slot lower_lazy(Lower *L, FeNode *n, int is_catch); +static Slot wrapper_payload(Lower *L, Slot w, const FeType *t); +static unsigned scratch(Lower *L, const FeType *t, const char *why); +static int uses_niche(const FeType *t); +static FeIrType tag_type(const FeType *t); +static void run_deferred(Lower *L, unsigned from); +static unsigned declare_var(Lower *L, const char *cname, const FeType *t, + const char *name); +static void indexable_parts(Lower *L, Slot base, const FeType *t, + unsigned *data, unsigned *length, FeNode *n); static void fail(Lower *L, const char *why, FeNode *n) { @@ -204,6 +228,19 @@ static void guard(Lower *L, unsigned ok, FeIrTrap reason, unsigned long line) L->b = cont; } +/* A tag says which of the two things a wrapper holds. An optional is one byte + at the front unless the payload has a spare representation; an error union is + a two-byte error code, and zero means there is no error. */ +static FeIrType tag_type(const FeType *t) +{ + return t && t->kind == FE_TYPE_ERROR_UNION ? FE_IR_I16 : FE_IR_I8; +} + +static int uses_niche(const FeType *t) +{ + return t && t->kind == FE_TYPE_OPTIONAL && fe_m7_optional_uses_niche(t->elem); +} + /* Somewhere to build an aggregate that has no home of its own yet. */ static unsigned scratch(Lower *L, const FeType *t, const char *why) { @@ -235,6 +272,46 @@ static void indexable_parts(Lower *L, Slot base, const FeType *t, } } +/* ------------------------------------------------------- error codes ----- */ + +static void note_error_name(Lower *L, const char *name) +{ + unsigned i; + unsigned at; + if (!name || L->error_count >= 256) 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. */ + at = L->error_count; + while (at > 0 && strcmp(L->error_names[at - 1], name) > 0) { + L->error_names[at] = L->error_names[at - 1]; + --at; + } + L->error_names[at] = name; + ++L->error_count; +} + +static void collect_error_names(Lower *L, FeNode *n) +{ + FeNode *x; + if (!n) return; + if (n->kind == FE_N_MEMBER && n->a && n->a->kind == FE_N_IDENT && + n->a->text && !strcmp(n->a->text, "error") && n->b && n->b->text) + note_error_name(L, n->b->text); + collect_error_names(L, n->a); + collect_error_names(L, n->b); + collect_error_names(L, n->c); + for (x = n->children; x; x = x->next) collect_error_names(L, x); +} + +static long error_code(Lower *L, const char *name) +{ + unsigned i; + for (i = 0; i < L->error_count; ++i) + if (!strcmp(L->error_names[i], name)) return (long)(i + 1); + return 0; +} + /* ---------------------------------------------------------- expressions --- */ static FeIrOp binary_op(const char *op, int *is_cmp) @@ -363,7 +440,19 @@ static Slot lower_call(Lower *L, FeNode *n) return slot_value(fe_ir_call(L->m, L->b, rt, callee, args, count), rt); } +static Slot lower_expr_core(Lower *L, FeNode *n); + +/* Every expression may be standing where a wrapper is expected, so the wrap is + applied once, here, rather than at each place that could need it. */ static Slot lower_expr(Lower *L, FeNode *n) +{ + Slot v; + if (!n || L->failed) return slot_void(); + v = lower_expr_core(L, n); + return n->sem_context ? wrap_context(L, v, n) : v; +} + +static Slot lower_expr_core(Lower *L, FeNode *n) { FeType *t; FeIrType it; @@ -397,6 +486,8 @@ static Slot lower_expr(Lower *L, FeNode *n) unsigned a; unsigned b; FeIrType operand; + if (n->text && !strcmp(n->text, "orelse")) return lower_lazy(L, n, 0); + if (n->text && !strcmp(n->text, "catch")) return lower_lazy(L, n, 1); if (n->text && (!strcmp(n->text, "and") || !strcmp(n->text, "or"))) return lower_logical(L, n, !strcmp(n->text, "and")); op = binary_op(n->text, &is_cmp); @@ -410,6 +501,7 @@ static Slot lower_expr(Lower *L, FeNode *n) is_cmp ? FE_IR_I8 : operand); } case FE_N_UNARY: + if (n->text && !strcmp(n->text, "try")) return lower_try(L, n); if (n->text && !strcmp(n->text, "-")) { unsigned zero = fe_ir_const(L->m, L->b, it, 0); unsigned v = as_value(L, lower_expr(L, n->a), n->a); @@ -429,6 +521,19 @@ static Slot lower_expr(Lower *L, FeNode *n) fail(L, "this unary operator", n); return slot_void(); case FE_N_MEMBER: + /* `error.Name` is a member of the open default set: a code, and + nothing to look up. */ + if (n->a && n->a->kind == FE_N_IDENT && n->a->text && + !strcmp(n->a->text, "error") && n->b && n->b->text) + return slot_value(fe_ir_const(L->m, L->b, FE_IR_I16, + error_code(L, n->b->text)), + FE_IR_I16); + /* `.?` is the payload of an optional the checker already proved is + there. */ + if (n->text && !strcmp(n->text, ".?")) { + FeType *bt = n->a ? n->a->sem_type : 0; + return wrapper_payload(L, lower_expr(L, n->a), bt); + } /* `p.^` reads through a pointer. */ if (n->text && !strcmp(n->text, ".^")) { unsigned p = as_value(L, lower_expr(L, n->a), n->a); @@ -529,6 +634,168 @@ static Slot lower_expr(Lower *L, FeNode *n) } } +/* Run the `defer` blocks that are live, most recent first. A `return` in the + middle of a function still owes them, so every exit path calls this. */ +static void run_deferred(Lower *L, unsigned from) +{ + unsigned i; + for (i = L->defer_count; i > from; --i) lower_stmt(L, L->deferred[i - 1]); +} + +/* ------------------------------------------------------- wrappers -------- * + * An optional is a tag and a payload; an error union is an error code and a + * payload, where a code of zero means there is no error. Both are memory, and + * both are built the same way: write the tag, then write the value after it. + * -------------------------------------------------------------------------- */ + +static Slot wrap_context(Lower *L, Slot v, FeNode *n) +{ + FeType *want = n->sem_context; + unsigned local; + long payload_at; + if (!want) return v; + local = scratch(L, want, "wrapped"); + payload_at = (long)fe_type_payload_offset(want); + if (want->kind == FE_TYPE_OPTIONAL) { + if (fe_m7_is_null(n)) { + /* A payload with a spare representation uses it for "nothing" + instead of carrying a separate tag. */ + unsigned z = fe_ir_const(L->m, L->b, + uses_niche(want) ? FE_IR_PTR : FE_IR_I8, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), z, + uses_niche(want) ? FE_IR_PTR : FE_IR_I8); + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); + } + if (!uses_niche(want)) { + unsigned one = fe_ir_const(L->m, L->b, FE_IR_I8, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), one, FE_IR_I8); + } + store_into(L, fe_ir_at_local(local, payload_at), v, n, + ir_size(want->elem)); + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); + } + if (want->kind == FE_TYPE_ERROR_UNION) { + FeType *value_type = want->error_value; + if (n->sem_type && n->sem_type->is_error) { + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), + as_value(L, v, n), FE_IR_I16); + } else { + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), zero, FE_IR_I16); + if (value_type && value_type->kind != FE_TYPE_VOID) + store_into(L, fe_ir_at_local(local, payload_at), v, n, + ir_size(value_type)); + } + return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(want)); + } + return v; +} + +/* The tag of a wrapper that is already in memory. */ +static unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n) +{ + FeIrPlace p; + if (!w.is_place) { fail(L, "a wrapper with no place", n); return 0; } + p = w.place; + if (uses_niche(t)) return fe_ir_load(L->m, L->b, FE_IR_PTR, p); + return fe_ir_load(L->m, L->b, tag_type(t), p); +} + +static Slot wrapper_payload(Lower *L, Slot w, const FeType *t) +{ + FeType *payload = t ? (t->kind == FE_TYPE_ERROR_UNION ? t->error_value + : t->elem) : 0; + FeIrPlace p = w.place; + (void)L; + p.offset += (long)fe_type_payload_offset(t); + return slot_place(p, ir_type(payload), ir_size(payload)); +} + +/* Leave the function with this error code, after the deferred blocks. */ +static void return_error(Lower *L, unsigned err, FeNode *n) +{ + FeType *ret = L->ret_type; + unsigned local = scratch(L, ret, "failure"); + fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), err, FE_IR_I16); + run_deferred(L, 0); + if (L->fn->returns_by_address) { + unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(L->ret_local, 0)); + fe_ir_copy(L->m, L->b, fe_ir_at_temp(dst, 0), fe_ir_at_local(local, 0), + ir_size(ret)); + fe_ir_ret(L->b, 0, 0); + return; + } + fe_ir_ret(L->b, fe_ir_load(L->m, L->b, ir_type(ret), + fe_ir_at_local(local, 0)), 1); + (void)n; +} + +/* `try e` -- if e failed, leave with its error; otherwise the value. */ +static Slot lower_try(Lower *L, FeNode *n) +{ + FeType *t = n->a ? n->a->sem_type : 0; + Slot e = lower_expr(L, n->a); + unsigned err = wrapper_tag(L, e, t, n); + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I16, 0); + unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I16, err, zero, 1); + FeIrBlock *bad = new_block(L); + FeIrBlock *good = new_block(L); + fe_ir_br(L->b, ok, good->id, bad->id); + L->b = bad; + return_error(L, err, n); + L->b = good; + return wrapper_payload(L, e, t); +} + +/* `e orelse d` and `e catch d` both mean "the value, or that instead". The + right-hand side is only evaluated when it is needed, so it is a branch. */ +static Slot lower_lazy(Lower *L, FeNode *n, int is_catch) +{ + FeType *t = n->a ? n->a->sem_type : 0; + FeType *payload = t ? (is_catch ? t->error_value : t->elem) : 0; + Slot e; + unsigned tag; + unsigned zero; + unsigned ok; + unsigned result; + FeIrBlock *other; + FeIrBlock *join; + FeIrBlock *have; + e = lower_expr(L, n->a); + tag = wrapper_tag(L, e, t, n); + zero = fe_ir_const(L->m, L->b, is_catch || uses_niche(t) ? FE_IR_PTR + : FE_IR_I8, 0); + /* An error union is fine when its code is zero; an optional is fine when + its tag is not. */ + ok = fe_ir_binary(L->m, L->b, is_catch ? FE_IR_EQ : FE_IR_NE, + is_catch ? FE_IR_I16 : (uses_niche(t) ? FE_IR_PTR + : FE_IR_I8), + tag, zero, 1); + result = scratch(L, payload, "result"); + have = new_block(L); + other = new_block(L); + join = new_block(L); + fe_ir_br(L->b, ok, have->id, other->id); + L->b = have; + store_into(L, fe_ir_at_local(result, 0), wrapper_payload(L, e, t), n, + ir_size(payload)); + fe_ir_jmp(L->b, join->id); + L->b = other; + if (is_catch && n->c) { + /* The block form handles the error and must not fall through with a + value, so whatever it leaves behind is what the checker allowed. */ + lower_stmt(L, n->c); + } else { + Slot d = lower_expr(L, n->b); + store_into(L, fe_ir_at_local(result, 0), d, n->b, ir_size(payload)); + } + fe_ir_jmp(L->b, join->id); + L->b = join; + return slot_place(fe_ir_at_local(result, 0), ir_type(payload), + ir_size(payload)); +} + /* ----------------------------------------------------------- statements --- */ static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, @@ -545,8 +812,13 @@ static void store_into(Lower *L, FeIrPlace dst, Slot value, FeNode *n, static void lower_return(Lower *L, FeNode *n) { Slot v; - if (!n->a) { fe_ir_ret(L->b, 0, 0); return; } + if (!n->a) { run_deferred(L, 0); fe_ir_ret(L->b, 0, 0); return; } + /* The value is computed before the deferred blocks run, because they may + destroy what it was read from. */ v = lower_expr(L, n->a); + if (v.type != FE_IR_MEM && v.is_place) + v = slot_value(as_value(L, v, n->a), v.type); + run_deferred(L, 0); if (L->fn->returns_by_address) { unsigned dst = fe_ir_load(L->m, L->b, FE_IR_PTR, fe_ir_at_local(L->ret_local, 0)); @@ -597,14 +869,153 @@ static void lower_while(Lower *L, FeNode *n) L->b = done; } +/* Three shapes share the keyword. + + for i in a..b { } counts + for x in thing { } walks, binding a reference to each element + for i, x in thing { } walks, binding the position as well + + The count is read once before the body, so a thing that grows underneath the + loop cannot walk past what was measured. The element binding is a reference + (`x.^` reads it), which is what lets a loop write back into the thing. */ +static void lower_for(Lower *L, FeNode *n) +{ + FeIrBlock *head; + FeIrBlock *body; + FeIrBlock *step; + FeIrBlock *done; + unsigned counter; + unsigned limit; + + if (n->c) { + /* The counting form: the variable is the count itself. */ + unsigned from = as_value(L, lower_expr(L, n->a), n->a); + unsigned to; + counter = declare_var(L, n->cname, 0, n->text); + L->fn->locals[counter].type = FE_IR_I32; + L->fn->locals[counter].size = 4; + L->fn->locals[counter].align = 4; + fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), from, FE_IR_I32); + to = as_value(L, lower_expr(L, n->c), n->c); + limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "limit"); + fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), to, FE_IR_I32); + head = new_block(L); + body = new_block(L); + step = new_block(L); + done = new_block(L); + fe_ir_jmp(L->b, head->id); + L->b = head; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(limit, 0)); + unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1); + fe_ir_br(L->b, more, body->id, done->id); + } + } else { + FeType *bt = n->a ? n->a->sem_type : 0; + FeType *elem = bt ? bt->elem : 0; + Slot base = lower_expr(L, n->a); + unsigned data; + unsigned length; + unsigned data_local; + unsigned item; + indexable_parts(L, base, bt, &data, &length, n); + data_local = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, "data"); + fe_ir_store(L->m, L->b, fe_ir_at_local(data_local, 0), data, FE_IR_PTR); + limit = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "count"); + fe_ir_store(L->m, L->b, fe_ir_at_local(limit, 0), length, FE_IR_I32); + /* With two names the first is the position and the second the element; + with one it is the element. */ + counter = fe_ir_local(L->m, L->fn, FE_IR_I32, 4, 4, "index"); + if (n->aux_cname) { + 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; + item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->aux_text); + 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; + } else { + item = fe_ir_local(L->m, L->fn, FE_IR_PTR, 4, 4, n->text); + 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; + } + { + unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I32, 0); + fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), zero, FE_IR_I32); + } + head = new_block(L); + body = new_block(L); + step = new_block(L); + done = new_block(L); + fe_ir_jmp(L->b, head->id); + L->b = head; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned e = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(limit, 0)); + unsigned more = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32, i, e, 1); + fe_ir_br(L->b, more, body->id, done->id); + } + L->b = body; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned scale = fe_ir_const(L->m, L->b, FE_IR_I32, + (long)ir_size(elem)); + unsigned off = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, i, + scale, 1); + unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR, + fe_ir_at_local(data_local, 0)); + unsigned at = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, p, + off, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(item, 0), at, FE_IR_PTR); + } + L->b = head; + } + + if (L->loop_depth < 32) { + L->break_target[L->loop_depth] = done->id; + L->continue_target[L->loop_depth] = step->id; + ++L->loop_depth; + } + L->b = body; + lower_stmt(L, n->b); + fe_ir_jmp(L->b, step->id); + L->b = step; + { + unsigned i = fe_ir_load(L->m, L->b, FE_IR_I32, + fe_ir_at_local(counter, 0)); + unsigned one = fe_ir_const(L->m, L->b, FE_IR_I32, 1); + unsigned next = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_I32, i, one, 1); + fe_ir_store(L->m, L->b, fe_ir_at_local(counter, 0), next, FE_IR_I32); + } + fe_ir_jmp(L->b, head->id); + if (L->loop_depth) --L->loop_depth; + L->b = done; +} + static void lower_stmt(Lower *L, FeNode *n) { FeNode *x; if (!n || L->failed) return; switch (n->kind) { - case FE_N_BLOCK: + case FE_N_BLOCK: { + unsigned outer = L->defer_count; for (x = n->children; x; x = x->next) lower_stmt(L, x); + /* Leaving a block normally runs what it deferred. An exit that jumped + away already ran them on its way out. */ + if (!L->b->terminated) run_deferred(L, outer); + L->defer_count = outer; return; + } case FE_N_LET: case FE_N_VAR: case FE_N_CONST: { @@ -644,6 +1055,12 @@ static void lower_stmt(Lower *L, FeNode *n) case FE_N_UNSAFE: lower_stmt(L, n->a); return; + case FE_N_DEFER: + if (L->defer_count < 32) L->deferred[L->defer_count++] = n->a; + return; + case FE_N_FOR: + lower_for(L, n); + return; default: fail(L, "this statement", n); return; @@ -697,6 +1114,10 @@ int fe_lower_program(FeCheck *c, FeIrModule *out) memset(&L, 0, sizeof L); L.c = c; L.m = out; + /* The codes have to be known while the bodies are lowered, so the names + are gathered from the whole build first. */ + for (u = 0; u < c->build->count; ++u) + collect_error_names(&L, c->build->units[u].ast.root); for (u = 0; u < c->build->count; ++u) { FeUnit *unit = &c->build->units[u]; c->ast = &unit->ast; diff --git a/fec/src/types.c b/fec/src/types.c index 1762bc9..71ef161 100644 --- a/fec/src/types.c +++ b/fec/src/types.c @@ -417,6 +417,20 @@ unsigned long fe_type_size(const FeType *t) return t ? t->size : 0; } +unsigned long fe_type_payload_offset(const FeType *t) +{ + if (!t) return 0; + if (t->kind == FE_TYPE_ERROR_UNION) { + if (!t->error_value || t->error_value->kind == FE_TYPE_VOID) return 2; + return round_up(2UL, fe_type_align(t->error_value)); + } + if (t->kind == FE_TYPE_OPTIONAL) { + if (fe_m7_optional_uses_niche(t->elem)) return 0; + return round_up(1UL, fe_type_align(t->elem)); + } + return 0; +} + unsigned fe_type_align(const FeType *t) { return t && t->align ? t->align : 1U; diff --git a/fec/src/types.h b/fec/src/types.h index 5ade57c..4a4a4a1 100644 --- a/fec/src/types.h +++ b/fec/src/types.h @@ -137,6 +137,10 @@ int fe_type_is_integer(const FeType *t); int fe_type_is_indexable(const FeType *t); const char *fe_type_c_name(const FeType *t, unsigned pointer_bits); unsigned long fe_type_size(const FeType *t); +/* Where the payload of an optional or an error union sits. The tag comes + first and the value is aligned after it; both the layout pass and the + code generator have to agree, so the rule lives in one place. */ +unsigned long fe_type_payload_offset(const FeType *t); unsigned fe_type_align(const FeType *t); #endif diff --git a/fec/tests/exec/errunion.fe b/fec/tests/exec/errunion.fe new file mode 100644 index 0000000..2293507 --- /dev/null +++ b/fec/tests/exec/errunion.fe @@ -0,0 +1,18 @@ +// EXIT:9 +unit errunion; + +fn half(v: i32) -> !i32 { + if v == 0 { return error.Empty; } + return v / 2; +} + +fn chain(v: i32) -> !i32 { + let h: i32 = try half(v); + return h + 1; +} + +fn main() -> i32 { + let good: i32 = chain(16) catch 100; + let bad: i32 = chain(0) catch 0; + return good + bad; +} diff --git a/fec/tests/exec/forloop.fe b/fec/tests/exec/forloop.fe new file mode 100644 index 0000000..8eb3cc2 --- /dev/null +++ b/fec/tests/exec/forloop.fe @@ -0,0 +1,11 @@ +// EXIT:60 +unit forloop; + +fn main() -> i32 { + let a: [5]i32 = [4, 8, 12, 16, 20]; + var sum: i32 = 0; + for v in a { + sum = sum + v.^; + } + return sum; +} diff --git a/fec/tests/exec/optional.fe b/fec/tests/exec/optional.fe new file mode 100644 index 0000000..8908b84 --- /dev/null +++ b/fec/tests/exec/optional.fe @@ -0,0 +1,15 @@ +// EXIT:42 +unit optional; + +fn pick(flag: bool) -> ?i32 { + if flag { return 42; } + return null; +} + +fn main() -> i32 { + let a: ?i32 = pick(true); + let b: ?i32 = pick(false); + let x: i32 = a orelse 0; + let y: i32 = b orelse 0; + return x + y; +}