Files
doslang-mirror/fec/src/lowerexp.c
T
coolguy 730bcac282 audit: SPEC-파서 괴리 일곱을 정리한다
구현 셋, SPEC 다섯. 어느 쪽이 틀렸는지는 항목마다 따로 판정했다.

구현:
- ~ 를 넣었다. ^ 는 xor 과 .^ 가 가져가서 비트 NOT 이 자기 철자를 못 갖고
  있었다. 렉서·파서·검사·lowering(전부 1 과의 xor).
- 전역 static/var 는 타입을 적는다. 다른 유닛이 읽는 링커 심볼이라 초기값의
  생김새에 타입을 맡기면 그쪽이 보는 것이 달라진다. const 는 그대로 추론한다.
- error code 는 정수 리터럴 하나다. 식이면 중복도 예약된 0 도 검사할 수 없다.

SPEC:
- 최상위 comptime if 를 뺐다. comptime 조건은 타입 술어뿐인데(§7.5) 유닛
  바깥에는 바인딩된 타입 파라미터가 없어 물어볼 것이 없다. §11 v0.2.
- 타입 이름은 [binding.]Name 이다. import 가 마지막 segment 를 바인딩하므로
  점 둘 이상은 만들어질 수 없는데 문법이 unit_path 를 쓰고 있었다.
- catch 의 EBNF 가 §4.6 보다 넓었다. 값을 주는 짧은 형태와 에러를 받는 블록
  형태 둘로 나눠 적었다.
- | 와 ^ 를 한 단계로 둔 것을 쪼갰다. 합치면 a | b ^ c 가 좌결합으로
  (a|b)^c 가 되어 C 에서 온 사람을 속인다. 구현이 C 순서로 옳았다.
- 마지막 필드 쉼표 생략을 명세에 적었다. enum 은 이미 허용하고 있었다.

그리고 tests/run.py 가 마커를 진단 스트림에만 맞춘다. --dump-ast 모드에서는
AST 덤프가 stdout 으로 먼저 나와서 parse/ fixture 는 마커를 쓸 수 없었다.

249/249, 39/39.
2026-08-17 16:52:12 +09:00

561 lines
24 KiB
C

#include "lowerpri.h"
Slot lower_expr_core(Lower *L, FeNode *n)
{
FeType *t;
FeIrType it;
if (!n || L->failed) return slot_void();
t = n->sem_type;
it = ir_type(t);
switch (n->kind) {
case FE_N_LITERAL:
if (n->text && n->text[0] == '"') {
/* The bytes live in the image; the value is a pointer to them and
how many there are. The lexer keeps the quotes and the escapes,
so this is where `
` becomes one byte. */
char text[1024];
unsigned long raw = strlen(n->text);
unsigned long len = 0;
unsigned long i;
const char *label;
unsigned local;
unsigned p;
unsigned c;
if (raw >= 2) raw -= 2;
for (i = 0; i < raw && len + 1 < sizeof text; ++i) {
char ch = n->text[1 + i];
if (ch == 92 && i + 1 < raw) { /* a backslash */
++i;
switch (n->text[1 + i]) {
case 'n': ch = 10; break;
case 't': ch = 9; break;
case 'r': ch = 13; break;
case '0': ch = 0; break;
default: ch = n->text[1 + i]; break;
}
}
text[len++] = ch;
}
label = fe_ir_string(L->m, text, len);
if (!label) { fail(L, "a string literal", n); return slot_void(); }
local = scratch(L, t, "text");
p = fe_ir_addr(L->m, L->b, fe_ir_at_global(label, 0));
fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_PTR_OFFSET), p,
FE_IR_PTR);
c = fe_ir_const(L->m, L->b, FE_IR_I32, (long)len);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, SLICE_LEN_OFFSET), c,
FE_IR_I32);
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
return slot_value(fe_ir_const(L->m, L->b,
it == FE_IR_VOID ? FE_IR_I32 : it,
literal_value(n)),
it == FE_IR_VOID ? FE_IR_I32 : it);
case FE_N_IDENT: {
LowerVar *var = find_var(L, n->cname);
if (var) {
if (var->by_address) {
unsigned p = fe_ir_load(L->m, L->b, FE_IR_PTR,
fe_ir_at_local(var->local, 0));
return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t));
}
return slot_place(fe_ir_at_local(var->local, 0), it, ir_size(t));
}
if (n->cname)
return slot_place(fe_ir_at_global(n->cname, 0), it, ir_size(t));
fail(L, "an unresolved name", n);
return slot_void();
}
case FE_N_BINARY: {
int is_cmp = 0;
FeIrOp op;
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);
/* Comparing an optional with `null` asks about its tag, not about the
bytes of the whole wrapper -- which has no value form at all. */
if ((op == FE_IR_EQ || op == FE_IR_NE) && n->a && n->b) {
FeNode *w = fe_m7_is_null(n->b) ? n->a :
(fe_m7_is_null(n->a) ? n->b : 0);
FeType *wt = w ? w->sem_type : 0;
if (wt && wt->kind == FE_TYPE_OPTIONAL) {
Slot s = lower_expr(L, w);
unsigned t0;
unsigned z;
if (!s.is_place) {
fail(L, "an optional with no place", w);
return slot_void();
}
t0 = wrapper_tag(L, s, wt, w);
z = fe_ir_const(L->m, L->b,
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, 0);
return slot_value(fe_ir_binary(L->m, L->b, op,
uses_niche(wt) ? FE_IR_PTR : FE_IR_I8, t0, z, 1),
FE_IR_I8);
}
}
operand = ir_type(n->a ? n->a->sem_type : 0);
if (operand == FE_IR_VOID || operand == FE_IR_MEM) operand = FE_IR_I32;
a = as_value(L, lower_expr(L, n->a), n->a);
b = as_value(L, lower_expr(L, n->b), n->b);
return slot_value(fe_ir_binary(L->m, L->b, op, operand, a, b,
type_is_unsigned(n->a ? n->a->sem_type
: 0)),
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);
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_SUB, it, zero, v,
0), it);
}
if (n->text && !strcmp(n->text, "~")) {
/* Every bit flipped is every bit exchanged with a one. */
unsigned ones = fe_ir_const(L->m, L->b, it, -1L);
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_XOR, it, v, ones,
0), it);
}
if (n->text && !strcmp(n->text, "not")) {
unsigned zero = fe_ir_const(L->m, L->b, FE_IR_I8, 0);
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
return slot_value(fe_ir_binary(L->m, L->b, FE_IR_EQ, FE_IR_I8, v,
zero, 0), FE_IR_I8);
}
if (n->text && (!strcmp(n->text, "&") || !strcmp(n->text, "&mut"))) {
Slot inner = lower_expr(L, n->a);
return slot_value(as_address(L, inner, n->a), FE_IR_PTR);
}
fail(L, "this unary operator", n);
return slot_void();
case FE_N_MEMBER:
/* A variant used as a value carries nothing but its tag. When no
variant of the enum carries anything the whole value is that tag;
otherwise it is a tag sitting in front of an unused payload. */
if (t && t->kind == FE_TYPE_ENUM && n->b && n->b->text) {
FeVariantType *v = fe_type_variant(t, n->b->text);
if (v && !enum_has_payload(t))
return slot_value(fe_ir_const(L->m, L->b, ir_type(t),
(long)v->tag), ir_type(t));
if (v && !v->field_count) {
unsigned local = scratch(L, t, "variant");
unsigned tag = fe_ir_const(L->m, L->b, tag_type_of(t),
(long)v->tag);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), tag,
tag_type_of(t));
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM,
ir_size(t));
}
}
/* `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 -- except for an owned slice, whose
pointer and length are the value itself, so there is nothing to
step through. */
if (n->text && !strcmp(n->text, ".^")) {
Slot base = lower_expr(L, n->a);
unsigned p;
if (base.type == FE_IR_MEM)
return slot_place(base.place, it, ir_size(t));
p = as_value(L, base, n->a);
return slot_place(fe_ir_at_temp(p, 0), it, ir_size(t));
}
/* `.n` is how many elements there are, which an array knows at
compile time and a slice carries beside its pointer. Only for those:
a struct is free to have a field called `n`, and reading it as a
length would quietly hand back the wrong four bytes. */
if (n->b && n->b->text && !strcmp(n->b->text, "n") &&
n->a && n->a->sem_type &&
(n->a->sem_type->kind == FE_TYPE_ARRAY ||
n->a->sem_type->kind == FE_TYPE_SLICE ||
n->a->sem_type->kind == FE_TYPE_STR)) {
FeType *bt = n->a->sem_type;
Slot base;
if (bt && bt->kind == FE_TYPE_ARRAY)
return slot_value(fe_ir_const(L->m, L->b, FE_IR_I32,
(long)bt->length), FE_IR_I32);
base = lower_expr(L, n->a);
if (!base.is_place) { fail(L, "a length of a temporary", n); return slot_void(); }
base.place.offset += SLICE_LEN_OFFSET;
return slot_place(base.place, FE_IR_I32, 4);
}
/* A field is a constant offset from the base. */
{
FeType *base = n->a ? n->a->sem_type : 0;
FeFieldType *field;
Slot b;
if (base && (base->kind == FE_TYPE_REF ||
base->kind == FE_TYPE_OWNED)) base = base->elem;
field = fe_type_field(base, n->b && n->b->text ? n->b->text : "");
/* `binding.name` is not a field of anything: it is a constant or a
global in another unit, and the checker already turned it into a
link name. */
if (!field && n->cname)
return slot_place(fe_ir_at_global(n->cname, 0), it,
ir_size(t));
if (!field) { fail(L, "an unresolved field", n); return slot_void(); }
b = lower_expr(L, n->a);
if (n->a->sem_type && (n->a->sem_type->kind == FE_TYPE_REF ||
n->a->sem_type->kind == FE_TYPE_OWNED)) {
unsigned p = as_value(L, b, n->a);
return slot_place(fe_ir_at_temp(p, (long)field->offset), it,
ir_size(t));
}
if (!b.is_place) { fail(L, "a field of a temporary", n); return slot_void(); }
b.place.offset += (long)field->offset;
return slot_place(b.place, it, ir_size(t));
}
case FE_N_INDEX: {
FeType *bt = n->a ? n->a->sem_type : 0;
FeType *elem = bt ? bt->elem : 0;
Slot base;
unsigned data;
unsigned length;
unsigned index;
unsigned scale;
unsigned offset;
unsigned addr;
if (n->flags & FE_NODE_SLICE) return lower_slice(L, n);
base = lower_expr(L, n->a);
indexable_parts(L, base, bt, &data, &length, n);
index = as_value(L, lower_expr(L, n->b), n->b);
if (!L->c->no_checks) {
unsigned ok = fe_ir_binary(L->m, L->b, FE_IR_LT, FE_IR_I32,
index, length, 1);
guard(L, ok, FE_TRAP_BOUNDS, n->loc.line);
}
scale = fe_ir_const(L->m, L->b, FE_IR_I32, (long)ir_size(elem));
offset = fe_ir_binary(L->m, L->b, FE_IR_MUL, FE_IR_I32, index, scale, 1);
addr = fe_ir_binary(L->m, L->b, FE_IR_ADD, FE_IR_PTR, data, offset, 1);
return slot_place(fe_ir_at_temp(addr, 0), ir_type(elem), ir_size(elem));
}
case FE_N_ARRAY_INIT: {
unsigned local = scratch(L, t, "array");
FeType *elem = t ? t->elem : 0;
unsigned long step = ir_size(elem);
long at = 0;
FeNode *x;
for (x = n->children; x; x = x->next) {
Slot v = lower_expr(L, x);
store_into(L, fe_ir_at_local(local, at), v, x, step);
at += (long)step;
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
case FE_N_STRUCT_INIT: {
unsigned local;
FeNode *f;
/* `Enum.Variant{ .. }` builds a variant, not a struct: the tag first,
then the named fields inside the payload area. */
if (t && t->kind == FE_TYPE_ENUM && n->a && n->a->kind == FE_N_MEMBER) {
const FeVariantType *v = fe_type_variant(t,
n->a->b && n->a->b->text ? n->a->b->text : "");
long base = (long)fe_type_payload_offset(t);
unsigned tag;
if (!v) { fail(L, "an unknown variant", n); return slot_void(); }
local = scratch(L, t, "variant");
tag = fe_ir_const(L->m, L->b, tag_type_of(t), (long)v->tag);
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), tag,
tag_type_of(t));
for (f = n->children; f; f = f->next) {
unsigned i;
if (f->kind != FE_N_FIELD) continue;
for (i = 0; i < v->field_count; ++i)
if (f->text && v->fields[i].name &&
!strcmp(v->fields[i].name, f->text)) break;
if (i == v->field_count) {
fail(L, "an unknown variant field", f);
return slot_void();
}
store_into(L, fe_ir_at_local(local,
base + (long)v->fields[i].offset),
lower_expr(L, f->a), f, ir_size(v->fields[i].type));
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
local = scratch(L, t, "struct");
for (f = n->children; f; f = f->next) {
FeFieldType *field;
Slot v;
if (f->kind != FE_N_FIELD) continue;
field = fe_type_field(t, f->text);
if (!field) { fail(L, "an unresolved field", f); return slot_void(); }
v = lower_expr(L, f->a);
store_into(L, fe_ir_at_local(local, (long)field->offset), v, f,
ir_size(field->type));
}
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
}
case FE_N_CALL:
return lower_call(L, n);
case FE_N_TYPE:
/* `x as T`: the operand is `a` and the target type is the node's own.
Between integers this only changes how wide the value is and whether
the top bits repeat the sign. */
if (n->a) {
FeType *from = n->a->sem_type;
unsigned v = as_value(L, lower_expr(L, n->a), n->a);
if (ir_type(from) == it) return slot_value(v, it);
return slot_value(fe_ir_cast(L->m, L->b, ir_type(from), it, v,
type_is_unsigned(from)), it);
}
fail(L, "this type expression", n);
return slot_void();
case FE_N_EXPR:
return lower_expr(L, n->a);
default:
fail(L, "this expression", n);
return slot_void();
}
}
/* The link name of the `drop` method for this type, found through the instance
the checker recorded. */
const char *drop_name(Lower *L, const FeType *t)
{
unsigned i;
FeNode *method = 0;
if (!t || !t->decl_node) return 0;
for (method = t->decl_node->children; method; method = method->next)
if (method->kind == FE_N_FN && method->text &&
!strcmp(method->text, "drop")) break;
if (!method) return 0;
for (i = 0; i < L->c->instance_count; ++i)
if (L->c->instances[i].decl == method &&
L->c->instances[i].owner == t)
return L->c->instances[i].cname;
return method->cname;
}
/* Let go of one value sitting at `at`. A type that says how to let go of
itself is asked first; then whatever it holds is let go of in turn, so a
struct that owns a struct that owns a buffer settles all three without
anyone writing a `drop` (SPEC 5 R1). */
void release_at(Lower *L, const FeType *t, FeIrPlace at)
{
unsigned args[1];
unsigned i;
if (!t) return;
if (t->has_drop) {
const char *how = drop_name(L, t);
args[0] = fe_ir_addr(L->m, L->b, at);
if (how) fe_ir_call(L->m, L->b, FE_IR_VOID, how, args, 1);
}
if (t->kind == FE_TYPE_OWNED) {
FeIrPlace p = at;
if (t->elem && t->elem->kind == FE_TYPE_SLICE)
p.offset += SLICE_PTR_OFFSET;
args[0] = fe_ir_load(L->m, L->b, FE_IR_PTR, p);
fe_ir_call(L->m, L->b, FE_IR_VOID, "fe_rt_free", args, 1);
return;
}
if (t->kind == FE_TYPE_STRUCT)
for (i = 0; i < t->field_count; ++i) {
FeIrPlace p = at;
if (!needs_release(t->fields[i].type)) continue;
p.offset += (long)t->fields[i].offset;
release_at(L, t->fields[i].type, p);
}
}
/* Settle what a scope owes, most recent first. A `return` in the middle of a
function still owes everything, so every exit path calls this. */
void run_deferred(Lower *L, unsigned from)
{
unsigned i;
for (i = L->owed_count; i > from; --i) {
if (L->owed[i - 1].block) {
lower_stmt(L, L->owed[i - 1].block);
continue;
}
{
/* Release only where the value is still here. */
unsigned live = fe_ir_load(L->m, L->b, FE_IR_I8,
fe_ir_at_local(L->owed[i - 1].flag, 0));
FeIrBlock *doit = new_block(L);
FeIrBlock *skip = new_block(L);
FeType *t = L->owed[i - 1].type;
fe_ir_br(L->b, live, doit->id, skip->id);
L->b = doit;
release_at(L, t, fe_ir_at_local(L->owed[i - 1].local, 0));
fe_ir_jmp(L->b, skip->id);
L->b = skip;
}
}
}
/* ------------------------------------------------------- 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.
* -------------------------------------------------------------------------- */
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. */
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);
}
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. */
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. */
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. */
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 --- */