harec

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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:
Msrc/emit.c | 19+++++++++++--------
Msrc/gen.c | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/qtype.c | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Atests/910-tagged.ha | 43+++++++++++++++++++++++++++++++++++++++++++
Mtests/configure | 3++-
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