harec

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

commit be248126f80f3ebe585ddaf30e940b1e247b85d3
parent 9e06fc263b175db0e749fb39a8ddd1c0bf7b22fc
Author: Drew DeVault <sir@cmpwn.com>
Date:   Sat,  7 Aug 2021 10:12:21 +0200

gen: implement type assertions

Signed-off-by: Drew DeVault <sir@cmpwn.com>

Diffstat:
Msrc/gen.c | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mtests/13-tagged.ha | 2+-
Mtests/rt.c | 16++++++++++++++++
Mtests/rt.ha | 4++++
4 files changed, 86 insertions(+), 13 deletions(-)

diff --git a/src/gen.c b/src/gen.c @@ -170,6 +170,25 @@ gen_load(struct gen_context *ctx, struct gen_value object) return value; } +static void +gen_fixed_abort(struct gen_context *ctx, + struct location loc, enum fixed_aborts reason) +{ + int n = snprintf(NULL, 0, "%s:%d:%d", loc.path, loc.lineno, loc.colno); + char *s = xcalloc(1, n + 1); + snprintf(s, n, "%s:%d:%d", loc.path, loc.lineno, loc.colno); + struct expression eloc = {0}; + eloc.type = EXPR_CONSTANT; + eloc.result = &builtin_type_const_str; + eloc.constant.string.value = s; + eloc.constant.string.len = n - 1; + struct gen_value msg = gen_expr(ctx, &eloc); + struct qbe_value qmsg = mkqval(ctx, &msg); + struct qbe_value rtabort = mkrtfunc(ctx, "rt.abort_fixed"); + struct qbe_value tmp = constl(reason); + pushi(ctx->current, NULL, Q_CALL, &rtabort, &qmsg, &tmp, NULL); +} + static struct gen_value gen_autoderef(struct gen_context *ctx, struct gen_value val) { @@ -532,6 +551,33 @@ gen_expr_type_test(struct gen_context *ctx, const struct expression *expr) return result; } +static void +gen_type_assertion(struct gen_context *ctx, + const struct expression *expr, + struct qbe_value base) +{ + const struct type *want = expr->result; + struct qbe_value tag = mkqtmp(ctx, + qtype_lookup(ctx, &builtin_type_uint, false), + ".%d"); + enum qbe_instr load = load_for_type(ctx, &builtin_type_uint); + pushi(ctx->current, &tag, load, &base, NULL); + struct qbe_value expected = constl(want->id); + struct gen_value result = mktemp(ctx, &builtin_type_bool, ".%d"); + struct qbe_value qr = mkqval(ctx, &result); + pushi(ctx->current, &qr, Q_CEQW, &tag, &expected, NULL); + + struct qbe_statement failedl, passedl; + struct qbe_value bfailed = mklabel(ctx, &failedl, "failed.%d"); + struct qbe_value bpassed = mklabel(ctx, &passedl, "passed.%d"); + pushi(ctx->current, NULL, Q_JNZ, &qr, &bpassed, &bfailed, NULL); + push(&ctx->current->body, &failedl); + + gen_fixed_abort(ctx, expr->loc, ABORT_TYPE_ASSERTION); + + push(&ctx->current->body, &passedl); +} + static struct gen_value gen_expr_cast(struct gen_context *ctx, const struct expression *expr) { @@ -540,12 +586,14 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr) case C_TEST: return gen_expr_type_test(ctx, expr); case C_ASSERTION: - assert(0); // TODO + assert(type_dealias(from)->storage == STORAGE_TAGGED); + assert(type_dealias(to)->storage != STORAGE_TAGGED); + // Fallthrough case C_CAST: break; } - // Casting to tagged union prefers _at form + // Casting to tagged union uses _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); @@ -556,18 +604,14 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr) return out; } - if (type_dealias(to)->storage == type_dealias(from)->storage - && to->size == from->size) { - struct gen_value value = gen_expr(ctx, expr->cast.value); - value.type = to; - return value; - } - - // Special cases + // Casting from tagged union to non-tagged union if (type_dealias(from)->storage == STORAGE_TAGGED) { - // Cast from tagged union struct gen_value value = gen_expr(ctx, expr->cast.value); struct qbe_value base = mkcopy(ctx, &value, ".%d"); + if (expr->cast.kind == C_ASSERTION) { + gen_type_assertion(ctx, expr, base); + } + struct qbe_value align = constl(from->align); pushi(ctx->current, &base, Q_ADD, &base, &align, NULL); struct gen_value storage = (struct gen_value){ @@ -577,6 +621,16 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr) }; return gen_load(ctx, storage); } + + // No conversion required + if (type_dealias(to)->storage == type_dealias(from)->storage + && to->size == from->size) { + struct gen_value value = gen_expr(ctx, expr->cast.value); + value.type = to; + return value; + } + + // Special cases switch (type_dealias(to)->storage) { case STORAGE_POINTER: if (type_dealias(from)->storage == STORAGE_SLICE) { @@ -585,7 +639,6 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr) } break; case STORAGE_VOID: - // Cast to void gen_expr(ctx, expr->cast.value); // Side-effects return gv_void; default: break; diff --git a/tests/13-tagged.ha b/tests/13-tagged.ha @@ -93,7 +93,7 @@ fn casts() void = { fn assertions() void = { let a: (u8 | u16) = 42u16; assert(a is u16); - // TODO: as + assert(a as u16 == 42u16); }; export fn main() void = { diff --git a/tests/rt.c b/tests/rt.c @@ -1,4 +1,5 @@ #include <stdlib.h> +#include <string.h> #include <unistd.h> void *c_memcpy(void *dest, const void *src, size_t n) { @@ -20,3 +21,18 @@ void c_abort(struct ha_str str) { write(2, "\n", 1); abort(); } + +const char *reasons[] = { + "Slice or array access out of bounds", // 0 + "Type assertion failed", // 1 + "Out of memory", // 2 +}; + +void c_abort_fixed(struct ha_str loc, int i) { + write(2, "Abort: ", 7); + write(2, loc.str, loc.len); + write(2, ": ", 2); + write(2, reasons[i], strlen(reasons[i])); + write(2, "\n", 1); + abort(); +}; diff --git a/tests/rt.ha b/tests/rt.ha @@ -1,6 +1,10 @@ fn c_abort(m: str) void; export @symbol("rt.abort") fn abort_(m: str) void = c_abort(m); +fn c_abort_fixed(loc: str, i: int) void; +export @symbol("rt.abort_fixed") fn abort_fixed(loc: str, i: int) void = + c_abort_fixed(loc, i); + fn c_memcpy(x: *void, y: *void, z: size) *void; export @symbol("rt.memcpy") fn memcpy(x: *void, y: *void, z: size) *void = { return c_memcpy(x, y, z);