diff --git a/fec/src/lowerexp.c b/fec/src/lowerexp.c index e43cfc5..869a36f 100644 --- a/fec/src/lowerexp.c +++ b/fec/src/lowerexp.c @@ -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; diff --git a/fec/src/lowerpri.h b/fec/src/lowerpri.h index a72262b..5cb9c33 100644 --- a/fec/src/lowerpri.h +++ b/fec/src/lowerpri.h @@ -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); diff --git a/fec/src/lowerprn.c b/fec/src/lowerprn.c index eea3923..83f7ecb 100644 --- a/fec/src/lowerprn.c +++ b/fec/src/lowerprn.c @@ -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) { diff --git a/fec/src/lowerstm.c b/fec/src/lowerstm.c index ff93d4d..527d04e 100644 --- a/fec/src/lowerstm.c +++ b/fec/src/lowerstm.c @@ -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; } - value = as_value(L, subject, n->a); + /* 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: diff --git a/fec/src/types.c b/fec/src/types.c index 4572476..1c1b707 100644 --- a/fec/src/types.c +++ b/fec/src/types.c @@ -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; diff --git a/fec/tests/exec/patterns.fe b/fec/tests/exec/patterns.fe new file mode 100644 index 0000000..dfd7eba --- /dev/null +++ b/fec/tests/exec/patterns.fe @@ -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; +}