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:
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);