lower: 태그드 유니온을 해체한다 -- match 페이로드와 if let
AST 도 IR 도 본질이 태그드 유니온이다. 태그 비교만 되고 안을 꺼내지 못하면
검사 없이 필드를 읽어야 하고, 그러면 안전성 이야기가 통째로 무너진다.
무언가를 담는 변이는 메모리다: 태그가 앞, 페이로드가 뒤. 태그를 읽는 것은
어느 쪽이든 같은 질문이고 자리만 다르다. 페이로드는 가리키지 않고 복사한다 --
매치한 것의 소유권을 arm 이 가져가는 것이 보통이고, 그게 허용되는지는 검사기가
이미 판단했다.
레코드 변이의 필드 오프셋을 아무도 기록하지 않고 있었다. 레이아웃이 크기는
재면서 자리는 버렸다.
Enum.Variant{...} 와 Enum.Variant(x) 와 페이로드 없는 Enum.Variant 를 모두
만든다. 마지막 것은 페이로드를 가진 enum 안에서는 태그만 든 값이다.
some 7 / none / point 3 4 / empty
exec.py 25/25.
This commit is contained in:
+44
-5
@@ -108,13 +108,23 @@ Slot lower_expr_core(Lower *L, FeNode *n)
|
||||
fail(L, "this unary operator", n);
|
||||
return slot_void();
|
||||
case FE_N_MEMBER:
|
||||
/* A payload-free variant used as a value is just its tag. */
|
||||
if (t && t->kind == FE_TYPE_ENUM && !enum_has_payload(t) &&
|
||||
n->b && n->b->text) {
|
||||
/* 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)
|
||||
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. */
|
||||
@@ -211,8 +221,37 @@ Slot lower_expr_core(Lower *L, FeNode *n)
|
||||
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM, ir_size(t));
|
||||
}
|
||||
case FE_N_STRUCT_INIT: {
|
||||
unsigned local = scratch(L, t, "struct");
|
||||
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;
|
||||
|
||||
@@ -87,6 +87,11 @@ typedef struct Slot {
|
||||
#define SLICE_LEN_OFFSET 4L
|
||||
|
||||
/* 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);
|
||||
unsigned wrapper_tag(Lower *L, Slot w, const FeType *t, FeNode *n);
|
||||
void bind_payload(Lower *L, Slot subject, const FeType *t,
|
||||
const FeVariantType *v, FeNode *arm);
|
||||
void fail(Lower *L, const char *why, FeNode *n);
|
||||
FeIrType ir_type_of(const FeType *t);
|
||||
int enum_has_payload(const FeType *t);
|
||||
|
||||
@@ -160,6 +160,27 @@ Slot lower_call(Lower *L, FeNode *n)
|
||||
if (lower_mem(L, n, &built)) return built;
|
||||
if (lower_print(L, n, &built)) return built;
|
||||
}
|
||||
/* `Enum.Variant(payload)` is a constructor, not a call. */
|
||||
if (ret && ret->kind == FE_TYPE_ENUM && n->a && n->a->kind == FE_N_MEMBER &&
|
||||
n->a->b && n->a->b->text) {
|
||||
const FeVariantType *v = fe_type_variant(ret, n->a->b->text);
|
||||
if (v) {
|
||||
unsigned local = scratch(L, ret, "variant");
|
||||
unsigned tag = fe_ir_const(L->m, L->b, tag_type_of(ret),
|
||||
(long)v->tag);
|
||||
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0), tag,
|
||||
tag_type_of(ret));
|
||||
if (v->field_count && n->children)
|
||||
store_into(L,
|
||||
fe_ir_at_local(local,
|
||||
(long)fe_type_payload_offset(ret) +
|
||||
(long)v->fields[0].offset),
|
||||
lower_expr(L, n->children), n->children,
|
||||
ir_size(v->fields[0].type));
|
||||
return slot_place(fe_ir_at_local(local, 0), FE_IR_MEM,
|
||||
ir_size(ret));
|
||||
}
|
||||
}
|
||||
if (!callee) { fail(L, "a call with no target", n); return slot_void(); }
|
||||
/* An aggregate result is written through a hidden first argument. */
|
||||
if (rt == FE_IR_MEM) {
|
||||
|
||||
+86
-1
@@ -274,6 +274,81 @@ void lower_for(Lower *L, FeNode *n)
|
||||
arm's pattern in turn. The checker already proved the arms cover everything,
|
||||
so falling off the end cannot happen in a program that compiled -- but the
|
||||
generated code has to go somewhere, and going to the join is right. */
|
||||
/* The width of a tag: an enum's own, or the byte an optional puts in front. */
|
||||
FeIrType tag_type_of(const FeType *t)
|
||||
{
|
||||
if (!t) return FE_IR_I8;
|
||||
if (t->kind == FE_TYPE_ERROR_UNION) return FE_IR_I16;
|
||||
if (t->kind == FE_TYPE_ENUM) return t->bits > 8U ? FE_IR_I16 : FE_IR_I8;
|
||||
return FE_IR_I8;
|
||||
}
|
||||
|
||||
/* Give an arm's names somewhere to live and put the variant's payload there.
|
||||
The payload is copied rather than pointed at: an arm that takes ownership of
|
||||
what it matched is the normal case, and the checker has already decided
|
||||
whether that was allowed. */
|
||||
void bind_payload(Lower *L, Slot subject, const FeType *t,
|
||||
const FeVariantType *v, FeNode *arm)
|
||||
{
|
||||
FeNode *name;
|
||||
unsigned i;
|
||||
long base;
|
||||
if (!v || !v->field_count || !arm->children || !subject.is_place) return;
|
||||
base = (long)fe_type_payload_offset(t);
|
||||
name = arm->children;
|
||||
for (i = 0; i < v->field_count && name; ++i, name = name->next) {
|
||||
FeType *ft = v->fields[i].type;
|
||||
unsigned local = declare_var(L, name->cname, ft, name->text);
|
||||
FeIrPlace from = subject.place;
|
||||
from.offset += base + (long)v->fields[i].offset;
|
||||
store_into(L, fe_ir_at_local(local, 0),
|
||||
slot_place(from, ir_type(ft), ir_size(ft)), name,
|
||||
ir_size(ft));
|
||||
}
|
||||
}
|
||||
|
||||
/* `if let Some(x) = opt { .. } else { .. }` -- and its None twin.
|
||||
|
||||
The optional is read once into a place, the tag decides the branch, and the
|
||||
binding gets what was inside. A binding whose type is a reference gets the
|
||||
address instead of a copy: the checker chose that when the payload was not
|
||||
something you may quietly duplicate. */
|
||||
void lower_if_let(Lower *L, FeNode *n)
|
||||
{
|
||||
FeType *opt = n->a ? n->a->sem_type : 0;
|
||||
Slot value = lower_expr(L, n->a);
|
||||
FeNode *binding = n->children;
|
||||
int is_some = n->aux_text && !strcmp(n->aux_text, "Some");
|
||||
unsigned tag;
|
||||
FeIrBlock *present;
|
||||
FeIrBlock *absent;
|
||||
FeIrBlock *join;
|
||||
if (!value.is_place) { fail(L, "if let over a temporary", n); return; }
|
||||
tag = wrapper_tag(L, value, opt, n);
|
||||
present = new_block(L);
|
||||
absent = new_block(L);
|
||||
join = new_block(L);
|
||||
fe_ir_br(L->b, tag, present->id, absent->id);
|
||||
/* Which side runs the body depends on which pattern was written. */
|
||||
L->b = is_some ? present : absent;
|
||||
if (is_some && binding) {
|
||||
FeType *bt = binding->sem_type;
|
||||
Slot payload = wrapper_payload(L, value, opt);
|
||||
unsigned local = declare_var(L, binding->cname, bt, binding->text);
|
||||
if (bt && (bt->kind == FE_TYPE_REF || bt->kind == FE_TYPE_RAW))
|
||||
fe_ir_store(L->m, L->b, fe_ir_at_local(local, 0),
|
||||
as_address(L, payload, n), FE_IR_PTR);
|
||||
else
|
||||
store_into(L, fe_ir_at_local(local, 0), payload, n, ir_size(bt));
|
||||
}
|
||||
lower_stmt(L, n->b);
|
||||
fe_ir_jmp(L->b, join->id);
|
||||
L->b = is_some ? absent : present;
|
||||
if (n->c) lower_stmt(L, n->c);
|
||||
fe_ir_jmp(L->b, join->id);
|
||||
L->b = join;
|
||||
}
|
||||
|
||||
void lower_match(Lower *L, FeNode *n)
|
||||
{
|
||||
FeType *t = n->a ? n->a->sem_type : 0;
|
||||
@@ -282,8 +357,16 @@ void lower_match(Lower *L, FeNode *n)
|
||||
unsigned value;
|
||||
FeIrBlock *join;
|
||||
FeNode *arm;
|
||||
if (it == FE_IR_MEM) { fail(L, "a match over a payload", n); return; }
|
||||
/* A variant that carries something is memory: the tag comes first and the
|
||||
payload after it. Reading the tag is then the same question either way,
|
||||
just from a different place. */
|
||||
if (it == FE_IR_MEM) {
|
||||
if (!subject.is_place) { fail(L, "a match over a temporary", n); return; }
|
||||
it = tag_type_of(t);
|
||||
value = fe_ir_load(L->m, L->b, it, subject.place);
|
||||
} else {
|
||||
value = as_value(L, subject, n->a);
|
||||
}
|
||||
join = new_block(L);
|
||||
for (arm = n->children; arm; arm = arm->next) {
|
||||
FeIrBlock *body;
|
||||
@@ -307,6 +390,7 @@ void lower_match(Lower *L, FeNode *n)
|
||||
next = new_block(L);
|
||||
fe_ir_br(L->b, same, body->id, next->id);
|
||||
L->b = body;
|
||||
bind_payload(L, subject, t, v, arm);
|
||||
lower_stmt(L, arm->a);
|
||||
fe_ir_jmp(L->b, join->id);
|
||||
L->b = next;
|
||||
@@ -362,6 +446,7 @@ void lower_stmt(Lower *L, FeNode *n)
|
||||
lower_return(L, n);
|
||||
return;
|
||||
case FE_N_IF:
|
||||
if (n->text && !strcmp(n->text, "if let")) { lower_if_let(L, n); return; }
|
||||
lower_if(L, n);
|
||||
return;
|
||||
case FE_N_WHILE:
|
||||
|
||||
@@ -443,6 +443,7 @@ unsigned long fe_type_payload_offset(const FeType *t)
|
||||
if (fe_m7_optional_uses_niche(t->elem)) return 0;
|
||||
return round_up(1UL, fe_type_align(t->elem));
|
||||
}
|
||||
if (t->kind == FE_TYPE_ENUM) return round_up(t->bits / 8U, t->align);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -551,6 +552,10 @@ static void layout_type(FeTypeCtx *ctx, FeType *t)
|
||||
layout_type(ctx, t->variants[i].fields[j].type);
|
||||
if (fe_type_align(t->variants[i].fields[j].type) > max_align)
|
||||
max_align = fe_type_align(t->variants[i].fields[j].type);
|
||||
/* Where this field sits inside the payload area, which the
|
||||
code generator needs and nobody was recording. */
|
||||
off = round_up(off, fe_type_align(t->variants[i].fields[j].type));
|
||||
t->variants[i].fields[j].offset = off;
|
||||
off += fe_type_size(t->variants[i].fields[j].type);
|
||||
}
|
||||
if (off > max_size) max_size = off;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
// EXIT:0
|
||||
// OUTPUT:some 7
|
||||
// OUTPUT:none
|
||||
// OUTPUT:point 3 4
|
||||
// OUTPUT:empty
|
||||
unit patterns;
|
||||
|
||||
enum Shape {
|
||||
Empty,
|
||||
Point(i32),
|
||||
Pair { x: i32, y: i32 },
|
||||
}
|
||||
|
||||
fn pick(flag: bool) -> ?i32 {
|
||||
if flag { return 7; }
|
||||
return null;
|
||||
}
|
||||
|
||||
fn describe(s: Shape) -> void {
|
||||
match s {
|
||||
Empty => { @print("empty\n"); }
|
||||
Point(v) => { @print("point {}\n", v); }
|
||||
Pair { x, y } => { @print("point {} {}\n", x, y); }
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> i32 {
|
||||
if let Some(v) = pick(true) { @print("some {}\n", v); }
|
||||
else { @print("unexpected\n"); }
|
||||
if let None = pick(false) { @print("none\n"); }
|
||||
else { @print("unexpected\n"); }
|
||||
describe(Shape.Pair{ x: 3, y: 4 });
|
||||
describe(Shape.Empty);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user