commit 1ee3a9573c990bc15544be8cbcb7ca2879c439cb
parent 0c895cbde85a7d553c59c2e5b5be584a202d9736
Author: Drew DeVault <sir@cmpwn.com>
Date: Thu, 5 Aug 2021 14:49:58 +0200
gen: initial tagged union implementation
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
5 files changed, 175 insertions(+), 11 deletions(-)
diff --git a/src/emit.c b/src/emit.c
@@ -48,18 +48,21 @@ static void
qemit_type(const struct qbe_def *def, FILE *out)
{
assert(def->kind == Q_TYPE);
- assert(def->type.base);
const struct type *base = def->type.base;
- char *tn = gen_typename(def->type.base);
- fprintf(out, "# %s [id: %u]\n", tn, def->type.base->id);
- free(tn);
- fprintf(out, "type :%s =", def->name);
- if (base->align != (size_t)-1) {
- fprintf(out, " align %zu", base->align);
+ if (base) {
+ char *tn = gen_typename(base);
+ fprintf(out, "# %s [id: %u]\n", tn, base->id);
+ free(tn);
+ fprintf(out, "type :%s =", def->name);
+ if (base->align != (size_t)-1) {
+ fprintf(out, " align %zu", base->align);
+ }
+ } else {
+ fprintf(out, "type :%s =", def->name);
}
fprintf(out, " {");
- bool is_union = type_dealias(base)->storage == STORAGE_UNION;
+ bool is_union = base == NULL || type_dealias(base)->storage == STORAGE_UNION;
const struct qbe_field *field = &def->type.fields;
while (field) {
if (is_union) {
diff --git a/src/gen.c b/src/gen.c
@@ -5,6 +5,7 @@
#include "expr.h"
#include "gen.h"
#include "scope.h"
+#include "typedef.h"
#include "types.h"
#include "util.h"
@@ -432,13 +433,77 @@ gen_expr_call(struct gen_context *ctx, const struct expression *expr)
return rval;
}
+static struct gen_value gen_expr_cast(struct gen_context *ctx,
+ const struct expression *expr);
+
+static char *
+gen_typename(const struct type *type)
+{
+ size_t sz = 0;
+ char *ptr = NULL;
+ FILE *f = open_memstream(&ptr, &sz);
+ emit_type(type, f);
+ fclose(f);
+ return ptr;
+}
+
+static void
+gen_expr_cast_at(struct gen_context *ctx,
+ const struct expression *expr, struct gen_value out)
+{
+ // This function is only concerned with casting to tagged unions, which
+ // is more efficient with the _at usage. For all other cases, it falls
+ // back to gen_expr_cast.
+ const struct type *to = expr->result, *from = expr->cast.value->result;
+ if (type_dealias(to)->storage != STORAGE_TAGGED) {
+ struct gen_value result = gen_expr_cast(ctx, expr);
+ if (!expr->terminates) {
+ gen_store(ctx, out, result);
+ }
+ return;
+ }
+
+ // Cast to tagged union
+ const struct type *subtype = tagged_select_subtype(to, from);
+ assert(subtype); // TODO: Casting between incompatible tagged unions
+
+ struct qbe_value qout = mkqval(ctx, &out);
+ struct qbe_value id = constw(subtype->id);
+ enum qbe_instr store = store_for_type(ctx, &builtin_type_uint);
+ char *tname = gen_typename(subtype);
+ pushc(ctx->current, "store tag for type %s", tname);
+ pushi(ctx->current, NULL, store, &id, &qout, NULL);
+ free(tname);
+
+ if (subtype->size == 0) {
+ return;
+ }
+
+ struct gen_value storage = mktemp(ctx, subtype, ".%d");
+ struct qbe_value qstor = mklval(ctx, &storage);
+ struct qbe_value offs = constl(to->align);
+ pushi(ctx->current, &qstor, Q_ADD, &qout, &offs, NULL);
+ gen_expr_at(ctx, expr->cast.value, storage);
+}
+
static struct gen_value
gen_expr_cast(struct gen_context *ctx, const struct expression *expr)
{
assert(expr->cast.kind == C_CAST); // TODO
const struct type *to = expr->result, *from = expr->cast.value->result;
- assert(type_dealias(to)->storage != STORAGE_TAGGED
- && type_dealias(from)->storage != STORAGE_TAGGED); // TODO
+
+ // Casting to tagged union prefers _at form
+ if (type_dealias(to)->storage == STORAGE_TAGGED) {
+ struct gen_value out = mktemp(ctx, expr->result, "object.%d");
+ struct qbe_value base = mkqval(ctx, &out);
+ struct qbe_value sz = constl(expr->result->size);
+ enum qbe_instr alloc = alloc_for_align(expr->result->align);
+ pushprei(ctx->current, &base, alloc, &sz, NULL);
+ gen_expr_cast_at(ctx, expr, out);
+ return out;
+ }
+
+ assert(type_dealias(from)->storage != STORAGE_TAGGED); // TODO
if (type_dealias(to)->storage == type_dealias(from)->storage
&& to->size == from->size) {
@@ -1007,6 +1072,9 @@ gen_expr_at(struct gen_context *ctx,
assert(out.kind != GV_CONST);
switch (expr->type) {
+ case EXPR_CAST:
+ gen_expr_cast_at(ctx, expr, out);
+ return;
case EXPR_CONSTANT:
gen_expr_const_at(ctx, expr, out);
return;
diff --git a/src/qtype.c b/src/qtype.c
@@ -16,6 +16,47 @@ sf_compar(const void *_a, const void *_b)
}
static const struct qbe_type *
+tagged_qtype(struct gen_context *ctx, const struct type *type)
+{
+ int n = snprintf(NULL, 0, "tags.%zd", ctx->id);
+ char *name = xcalloc(1, n + 1);
+ snprintf(name, n + 1, "tags.%zd", ctx->id);
+ ++ctx->id;
+
+ struct qbe_def *def = xcalloc(1, sizeof(struct qbe_def));
+ def->kind = Q_TYPE;
+ def->name = name;
+ def->exported = false;
+ def->type.stype = Q__AGGREGATE;
+ def->type.base = NULL;
+ def->type.name = name;
+ def->type.size = type->size - type->align;
+
+ struct qbe_field *field = &def->type.fields;
+ struct qbe_field **next = &field->next;
+ for (const struct type_tagged_union *tu = &type->tagged;
+ tu; tu = tu->next) {
+ if (tu->type->size == 0) {
+ if (!tu->next && *next) {
+ free(*next);
+ *next = NULL;
+ }
+ continue;
+ }
+ field->type = qtype_lookup(ctx, tu->type, true);
+ field->count = 1;
+ if (tu->next) {
+ field->next = xcalloc(1, sizeof(struct qbe_field));
+ next = &field->next;
+ field = field->next;
+ }
+ }
+
+ qbe_append_def(ctx->out, def);
+ return &def->type;
+}
+
+static const struct qbe_type *
aggregate_lookup(struct gen_context *ctx, const struct type *type)
{
for (struct qbe_def *def = ctx->out->defs; def; def = def->next) {
@@ -96,6 +137,14 @@ aggregate_lookup(struct gen_context *ctx, const struct type *type)
}
break;
case STORAGE_TAGGED:
+ field->type = &qbe_word; // XXX: ARCH
+ field->count = 1;
+ if (type->size != builtin_type_uint.size) {
+ field->next = xcalloc(1, sizeof(struct qbe_field));
+ field = field->next;
+ field->type = tagged_qtype(ctx, type);
+ field->count = 1;
+ }
break;
case STORAGE_ENUM:
case STORAGE_ALIAS:
diff --git a/tests/910-tagged.ha b/tests/910-tagged.ha
@@ -0,0 +1,43 @@
+fn totagged() void = {
+ // Simple case
+ let x: (int | void) = void;
+ let p = &x: *struct {
+ id: uint,
+ data: int,
+ };
+ assert(p.id == 3012680272);
+ x = 1337;
+ assert(p.id == 1737287038);
+ assert(p.data == 1337);
+
+ // Align of 4
+ let x: (int | f32 | void) = 1337;
+ let p = &x: *struct {
+ id: uint,
+ data: union {
+ idata: int,
+ fdata: f32,
+ },
+ };
+ assert(p.id == 1737287038);
+ assert(p.data.idata == 1337);
+ x = 13.37f32;
+ assert(p.id == 930681398);
+ assert(p.data.fdata == 13.37f32);
+
+ // Align of 8
+ let x: (size | void) = 1337z;
+ let p = &x: *struct {
+ id: uint,
+ data: size,
+ };
+ assert(p.id == 4119164483);
+ assert(p.data == 1337z);
+
+ // TODO: More cases
+};
+
+export fn main() int = {
+ totagged();
+ return 0;
+};
diff --git a/tests/configure b/tests/configure
@@ -13,7 +13,8 @@ tests() {
906-if \
907-casts \
908-loops \
- 909-defer
+ 909-defer \
+ 910-tagged
do
cat <<EOF
tests/$t: harec tests/$t.ha tests/rt.o