commit 20db8aa13ac26b27e7a60286dc283b65da24e14e
parent e3da1adc553f73caa8dd917bbb27900a6d448286
Author: Eyal Sawady <ecs@d2evs.net>
Date: Fri, 25 Feb 2022 16:45:22 +0000
types: overhaul flexible constants
And drop the binarithm hacks which were supporting the old
implementation
Signed-off-by: Eyal Sawady <ecs@d2evs.net>
Diffstat:
12 files changed, 354 insertions(+), 323 deletions(-)
diff --git a/include/types.h b/include/types.h
@@ -40,6 +40,7 @@ enum type_storage {
STORAGE_UNION,
STORAGE_FCONST,
STORAGE_ICONST,
+ STORAGE_RCONST,
};
struct type;
@@ -96,6 +97,14 @@ struct type_func {
unsigned int flags; // enum function_flags
};
+struct type_const {
+ intmax_t min, max;
+ uint32_t id;
+ const struct type ***refs;
+ size_t nrefs;
+ size_t zrefs;
+};
+
enum pointer_flags {
PTR_NULLABLE = 1 << 0,
};
@@ -149,6 +158,7 @@ struct type {
struct type_array array;
struct type_enum _enum;
struct type_func func;
+ struct type_const _const;
struct type_pointer pointer;
struct type_struct_union struct_union;
struct type_tagged_union tagged;
@@ -173,18 +183,24 @@ const struct type *tagged_select_subtype(
bool tagged_subset_compat(const struct type *to, const struct type *from);
const char *type_storage_unparse(enum type_storage storage);
-bool storage_is_flexible(enum type_storage storage);
bool type_is_signed(const struct type *type);
bool type_is_integer(const struct type *type);
bool type_is_numeric(const struct type *type);
bool type_is_float(const struct type *type);
+bool type_is_constant(const struct type *type);
bool type_has_error(const struct type *type);
uint32_t type_hash(const struct type *type);
+const struct type *promote_const(const struct type *a, const struct type *b);
bool type_is_assignable(const struct type *to, const struct type *from);
bool type_is_castable(const struct type *to, const struct type *from);
+const struct type *type_create_const(enum type_storage storage,
+ intmax_t min, intmax_t max);
+const struct type *lower_const(const struct type *old, const struct type *new);
+void const_refer(const struct type *type, const struct type **ref);
+
void builtin_types_init();
// Built-in type singletons
@@ -194,12 +210,10 @@ extern struct type
builtin_type_char,
builtin_type_f32,
builtin_type_f64,
- builtin_type_fconst,
builtin_type_i8,
builtin_type_i16,
builtin_type_i32,
builtin_type_i64,
- builtin_type_iconst,
builtin_type_int,
builtin_type_u8,
builtin_type_u16,
@@ -216,12 +230,10 @@ extern struct type
builtin_type_const_char,
builtin_type_const_f32,
builtin_type_const_f64,
- builtin_type_const_fconst,
builtin_type_const_i8,
builtin_type_const_i16,
builtin_type_const_i32,
builtin_type_const_i64,
- builtin_type_const_iconst,
builtin_type_const_int,
builtin_type_const_u8,
builtin_type_const_u16,
diff --git a/src/check.c b/src/check.c
@@ -700,6 +700,10 @@ type_promote(struct type_store *store,
return a->storage == STORAGE_ALIAS ? a : b;
}
+ if (type_is_constant(da) || type_is_constant(db)) {
+ return promote_const(a, b);
+ }
+
switch (da->storage) {
case STORAGE_ARRAY:
if (da->array.length == SIZE_UNDEFINED && da->array.members) {
@@ -780,22 +784,14 @@ type_promote(struct type_store *store,
return NULL;
// Handled above
case STORAGE_ALIAS:
- assert(0);
- // Invariant
case STORAGE_FCONST:
case STORAGE_ICONST:
+ case STORAGE_RCONST:
assert(0);
}
assert(0);
}
-static bool
-aexpr_is_flexible(const struct ast_expression *expr)
-{
- return expr->type == EXPR_CONSTANT
- && storage_is_flexible(expr->constant.storage);
-}
-
static void
check_expr_binarithm(struct context *ctx,
const struct ast_expression *aexpr,
@@ -854,62 +850,22 @@ check_expr_binarithm(struct context *ctx,
break;
}
- bool lflex = aexpr_is_flexible(aexpr->binarithm.lvalue),
- rflex = aexpr_is_flexible(aexpr->binarithm.rvalue);
struct expression *lvalue = xcalloc(1, sizeof(struct expression)),
*rvalue = xcalloc(1, sizeof(struct expression));
-
- if (hint && lflex && rflex) {
- check_expression(ctx, aexpr->binarithm.lvalue, lvalue, hint);
- check_expression(ctx, aexpr->binarithm.rvalue, rvalue, hint);
- } else if (lflex && rflex) {
- intmax_t l = aexpr->binarithm.lvalue->constant.ival,
- r = aexpr->binarithm.rvalue->constant.ival,
- max = l > r ? l : r, min = l < r ? l : r;
- enum type_storage storage = STORAGE_ICONST;
- if (min < 0) {
- if (max < ((intmax_t)1 << 7) - 1
- && min > -((intmax_t)1 << 8)) {
- storage = STORAGE_I8;
- } else if (max < ((intmax_t)1 << 15) - 1
- && min > -((intmax_t)1 << 16)) {
- storage = STORAGE_I16;
- } else if (max < ((intmax_t)1 << 31) - 1
- && min > -((intmax_t)1 << 32)) {
- storage = STORAGE_I32;
- } else {
- storage = STORAGE_I64;
- }
- } else {
- if (max < ((intmax_t)1 << 8)) {
- storage = STORAGE_U8;
- } else if (max < ((intmax_t)1 << 16)) {
- storage = STORAGE_U16;
- } else if (max < ((intmax_t)1 << 32)) {
- storage = STORAGE_U32;
- } else {
- storage = STORAGE_U64;
- }
- }
- assert(storage != STORAGE_ICONST);
- check_expression(ctx, aexpr->binarithm.lvalue, lvalue, builtin_type_for_storage(storage, false));
- check_expression(ctx, aexpr->binarithm.rvalue, rvalue, builtin_type_for_storage(storage, false));
- } else if (!lflex && rflex) {
- check_expression(ctx, aexpr->binarithm.lvalue, lvalue, hint);
- check_expression(ctx, aexpr->binarithm.rvalue, rvalue, lvalue->result);
- } else if (lflex && !rflex) {
- check_expression(ctx, aexpr->binarithm.rvalue, rvalue, hint);
- check_expression(ctx, aexpr->binarithm.lvalue, lvalue, rvalue->result);
- } else {
- check_expression(ctx, aexpr->binarithm.lvalue, lvalue, hint);
- check_expression(ctx, aexpr->binarithm.rvalue, rvalue, hint);
- }
+ struct ast_expression *alvalue = aexpr->binarithm.lvalue,
+ *arvalue = aexpr->binarithm.rvalue;
+ // XXX: Should hints be passed down?
+ (void)hint;
+ check_expression(ctx, alvalue, lvalue, NULL);
+ check_expression(ctx, arvalue, rvalue, NULL);
const struct type *p =
type_promote(ctx->store, lvalue->result, rvalue->result);
if (p == NULL) {
error(ctx, aexpr->loc, expr,
- "Cannot promote lvalue and rvalue");
+ "Cannot promote lvalue %s and rvalue %s",
+ gen_typename(lvalue->result),
+ gen_typename(rvalue->result));
return;
}
expr->result = &builtin_type_bool;
@@ -948,6 +904,7 @@ check_expr_binarithm(struct context *ctx,
if (!type_is_numeric(p) && type_dealias(p)->storage != STORAGE_POINTER
&& type_dealias(p)->storage != STORAGE_STRING
&& type_dealias(p)->storage != STORAGE_BOOL
+ && type_dealias(p)->storage != STORAGE_RCONST
&& type_dealias(p)->storage != STORAGE_RUNE) {
error(ctx, aexpr->loc, expr,
"Cannot perform equality test on %s type",
@@ -1050,14 +1007,16 @@ check_expr_binding(struct context *ctx,
}
}
- if (type->size == 0 || type->size == SIZE_UNDEFINED) {
+ if (!type_is_assignable(type, initializer->result)) {
error(ctx, aexpr->loc, expr,
- "Cannot create binding for type of zero or undefined size");
+ "Initializer is not assignable to binding type");
return;
}
- if (!type_is_assignable(type, initializer->result)) {
+ // XXX: Can we avoid this?
+ type = lower_const(type, NULL);
+ if (type->size == 0 || type->size == SIZE_UNDEFINED) {
error(ctx, aexpr->loc, expr,
- "Initializer is not assignable to binding type");
+ "Cannot create binding for type of zero or undefined size");
return;
}
binding->initializer = lower_implicit_cast(type, initializer);
@@ -1328,11 +1287,21 @@ check_expr_array(struct context *ctx,
if (!type) {
type = value->result;
} else {
+ if (!hint) {
+ // The promote_const in
+ // check_expression_constant might've caused the
+ // type to change out from under our feet
+ type = expr->constant.array->value->result;
+ }
if (!type_is_assignable(type, value->result)) {
error(ctx, item->value->loc, expr,
"Array members must be of a uniform type");
return;
}
+ if (!hint) {
+ // Ditto
+ type = expr->constant.array->value->result;
+ }
cur->value = lower_implicit_cast(type, cur->value);
}
@@ -1353,91 +1322,6 @@ check_expr_array(struct context *ctx,
expr->result = type_store_lookup_array(ctx->store, type, len, expand);
}
-static const struct type *
-lower_constant(const struct type *type, struct expression *expr)
-{
- assert(expr->type == EXPR_CONSTANT);
- type = type_dealias(type);
- if (type->storage == STORAGE_TAGGED) {
- const struct type *tag = NULL;
- for (const struct type_tagged_union *tu = &type->tagged; tu;
- tu = tu->next) {
- if (lower_constant(tu->type, expr)) {
- if (tag == NULL) {
- tag = tu->type;
- continue;
- }
- // Ambiguous
- switch (expr->result->storage) {
- case STORAGE_ICONST:
- return lower_constant(&builtin_type_int, expr);
- case STORAGE_FCONST:
- return lower_constant(&builtin_type_f64, expr);
- case STORAGE_RUNE:
- return lower_constant(&builtin_type_rune, expr);
- default:
- assert(0);
- }
- }
- }
- return tag;
- }
- if (type_is_float(type) && type_is_float(expr->result)) {
- return type;
- }
- if (!type_is_integer(type) && type->storage != STORAGE_RUNE) {
- return NULL;
- }
- if (type_is_signed(type)) {
- intmax_t max, min;
- switch (type->size) {
- case 1:
- max = INT8_MAX;
- min = INT8_MIN;
- break;
- case 2:
- max = INT16_MAX;
- min = INT16_MIN;
- break;
- case 4:
- max = INT32_MAX;
- min = INT32_MIN;
- break;
- case 8:
- max = INT64_MAX;
- min = INT64_MIN;
- break;
- default:
- assert(0);
- }
- if (expr->constant.ival <= max && expr->constant.ival >= min) {
- return type;
- }
- return NULL;
- }
- uintmax_t max;
- switch (type->size) {
- case 1:
- max = UINT8_MAX;
- break;
- case 2:
- max = UINT16_MAX;
- break;
- case 4:
- max = UINT32_MAX;
- break;
- case 8:
- max = UINT64_MAX;
- break;
- default:
- assert(0);
- }
- if (expr->constant.uval <= max) {
- return type;
- }
- return NULL;
-}
-
static void
check_expr_compound(struct context *ctx,
const struct ast_expression *aexpr,
@@ -1503,51 +1387,21 @@ check_expr_constant(struct context *ctx,
const struct type *hint)
{
expr->type = EXPR_CONSTANT;
- expr->result = builtin_type_for_storage(aexpr->constant.storage, false);
-
- if (expr->result && expr->result->storage == STORAGE_ICONST) {
- if (hint == NULL) {
- hint = builtin_type_for_storage(STORAGE_INT, false);
- }
- expr->constant.ival = aexpr->constant.ival;
- const struct type *type = lower_constant(hint, expr);
- if (!type) {
- // TODO: This error message is awful
- error(ctx, aexpr->loc, expr,
- "Integer constant out of range");
- return;
- }
- expr->result = type;
- }
-
- if (expr->result && expr->result->storage == STORAGE_FCONST) {
- if (hint == NULL) {
- hint = builtin_type_for_storage(STORAGE_F64, false);
- }
- expr->constant.fval = aexpr->constant.fval;
- const struct type *type = lower_constant(hint, expr);
- if (!type) {
- // TODO: This error message is awful
- error(ctx, aexpr->loc, expr,
- "Floating constant out of range");
- return;
- }
- expr->result = type;
- }
-
- if (expr->result && expr->result->storage == STORAGE_RUNE) {
- if (hint == NULL) {
- hint = builtin_type_for_storage(STORAGE_RUNE, false);
- }
- expr->constant.uval = aexpr->constant.uval;
- const struct type *type = lower_constant(hint, expr);
- if (!type) {
- // TODO: This error message is awful
- error(ctx, aexpr->loc, expr,
- "Rune constant out of range");
- return;
+ enum type_storage storage = aexpr->constant.storage;
+ expr->result = builtin_type_for_storage(storage, false);
+ if (storage == STORAGE_ICONST || storage == STORAGE_FCONST
+ || storage == STORAGE_RCONST) {
+ expr->result = type_create_const(storage,
+ aexpr->constant.ival, aexpr->constant.ival);
+ if (hint != NULL) {
+ const struct type *new =
+ promote_const(expr->result, hint);
+ if (new == NULL) {
+ expr->result = lower_const(expr->result, NULL);
+ } else {
+ expr->result = new;
+ }
}
- expr->result = type;
}
switch (aexpr->constant.storage) {
@@ -1567,7 +1421,7 @@ check_expr_constant(struct context *ctx,
case STORAGE_SIZE:
expr->constant.uval = aexpr->constant.uval;
break;
- case STORAGE_RUNE:
+ case STORAGE_RCONST:
expr->constant.rune = aexpr->constant.rune;
break;
case STORAGE_BOOL:
@@ -1597,6 +1451,7 @@ check_expr_constant(struct context *ctx,
case STORAGE_ALIAS:
case STORAGE_FUNCTION:
case STORAGE_POINTER:
+ case STORAGE_RUNE:
case STORAGE_SLICE:
case STORAGE_TAGGED:
case STORAGE_TUPLE:
@@ -2775,11 +2630,6 @@ check_expr_unarithm(struct context *ctx,
"Cannot perform binary NOT (~) on non-integer type");
return;
}
- if (type_is_signed(operand->result)) {
- error(ctx, aexpr->unarithm.operand->loc, expr,
- "Cannot perform binary NOT (~) on signed type");
- return;
- }
expr->result = operand->result;
break;
case UN_MINUS:
@@ -2789,11 +2639,6 @@ check_expr_unarithm(struct context *ctx,
"Cannot perform operation on non-numeric type");
return;
}
- if (!type_is_signed(operand->result)) {
- error(ctx, aexpr->unarithm.operand->loc, expr,
- "Cannot perform operation on unsigned type");
- return;
- }
expr->result = operand->result;
break;
case UN_ADDRESS:
@@ -2911,6 +2756,7 @@ check_expression(struct context *ctx,
break;
}
assert(expr->result);
+ const_refer(expr->result, &expr->result);
if (hint && hint->storage == STORAGE_VOID) {
if ((expr->result->flags & TYPE_ERROR) != 0) {
error(ctx, aexpr->loc, expr,
@@ -3068,7 +2914,8 @@ check_global(struct context *ctx,
expect(&adecl->init->loc,
type_is_assignable(type, initializer->result),
- "Constant type is not assignable from initializer type");
+ "Initializer type %s is not assignable to constant type %s",
+ gen_typename(initializer->result), gen_typename(type));
bool context = adecl->type
&& adecl->type->storage == STORAGE_ARRAY
@@ -3317,7 +3164,8 @@ scan_const(struct context *ctx, const struct ast_global_decl *decl)
}
expect(&decl->init->loc, type_is_assignable(type, initializer->result),
- "Constant type is not assignable from initializer type");
+ "Initializer type %s is not assignable to constant type %s",
+ gen_typename(initializer->result), gen_typename(type));
initializer = lower_implicit_cast(type, initializer);
struct expression *value =
@@ -3780,7 +3628,8 @@ check_internal(struct type_store *ts,
// TODO: This could be more detailed
expect(&loc, ctx.errors == NULL, "Invalid initializer");
expect(&loc, type_is_assignable(type, initializer->result),
- "Constant type is not assignable from initializer type");
+ "Initializer type %s is not assignable to constant type type %s",
+ gen_typename(initializer->result), gen_typename(type));
initializer = lower_implicit_cast(type, initializer);
struct expression *value =
xcalloc(1, sizeof(struct expression));
diff --git a/src/eval.c b/src/eval.c
@@ -49,6 +49,7 @@ itrunc(const struct type *type, uintmax_t val)
case STORAGE_U16:
return (uint16_t)val;
case STORAGE_U32:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
return (uint32_t)val;
case STORAGE_U64:
@@ -101,10 +102,9 @@ ftrunc(const struct type *type, double val)
{
if (type->storage == STORAGE_F32) {
return (float)val;
- } else if (type->storage == STORAGE_F64) {
- return val;
}
- assert(0);
+ assert(type_is_float(type));
+ return val;
}
enum eval_result
@@ -343,6 +343,7 @@ eval_const(struct context *ctx, struct expression *in, struct expression *out)
case STORAGE_INT:
case STORAGE_NULL:
case STORAGE_POINTER:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_SIZE:
case STORAGE_U16:
@@ -428,6 +429,7 @@ eval_cast(struct context *ctx, struct expression *in, struct expression *out)
case STORAGE_UINT:
case STORAGE_UINTPTR:
case STORAGE_SIZE:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
if (type_is_float(val.result)) {
out->constant.ival =
@@ -544,6 +546,7 @@ constant_default(struct context *ctx, struct expression *v)
case STORAGE_CHAR:
case STORAGE_ENUM:
case STORAGE_NULL:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_BOOL:
break; // calloc does this for us
diff --git a/src/gen.c b/src/gen.c
@@ -1450,6 +1450,8 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr)
struct gen_value intermediate;
struct qbe_value qintermediate;
+ from = lower_const(from, NULL);
+
enum qbe_instr op;
bool is_signed = type_is_signed(from);
enum type_storage fstor = type_dealias(from)->storage,
@@ -1483,7 +1485,6 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr)
assert(tstor == STORAGE_UINTPTR);
op = Q_COPY;
} else if (fstor == STORAGE_RUNE) {
- assert(tstor == STORAGE_U32);
op = Q_COPY;
} else if (type_is_float(from)) {
if (type_is_signed(to)) {
@@ -1576,6 +1577,7 @@ gen_expr_cast(struct gen_context *ctx, const struct expression *expr)
case STORAGE_FCONST:
case STORAGE_FUNCTION:
case STORAGE_ICONST:
+ case STORAGE_RCONST:
case STORAGE_STRING:
case STORAGE_STRUCT:
case STORAGE_TAGGED:
@@ -3103,6 +3105,7 @@ gen_data_item(struct gen_context *ctx, struct expression *expr,
struct qbe_def *def;
const struct expression_constant *constant = &expr->constant;
const struct type *type = type_dealias(expr->result);
+ type = lower_const(type, NULL);
if (constant->object) {
item->type = QD_SYMOFFS;
item->sym = ident_to_sym(&constant->object->ident);
@@ -3368,6 +3371,7 @@ gen_data_item(struct gen_context *ctx, struct expression *expr,
case STORAGE_FCONST:
case STORAGE_FUNCTION:
case STORAGE_ICONST:
+ case STORAGE_RCONST:
case STORAGE_NULL:
case STORAGE_VOID:
assert(0); // Invariant
diff --git a/src/lex.c b/src/lex.c
@@ -585,7 +585,7 @@ lex_string(struct lexer *lexer, struct token *out)
c = next(lexer, NULL, false);
assert(c == '\'');
out->token = T_LITERAL;
- out->storage = STORAGE_RUNE;
+ out->storage = STORAGE_RCONST;
return out->token;
default:
assert(0); // Invariant
@@ -1125,7 +1125,7 @@ token_str(const struct token *tok)
case STORAGE_FCONST:
snprintf(buf, sizeof(buf), "%lf", tok->fval);
break;
- case STORAGE_RUNE:
+ case STORAGE_RCONST:
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "'");
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "%s",
rune_unparse(tok->rune));
@@ -1141,6 +1141,7 @@ token_str(const struct token *tok)
case STORAGE_FUNCTION:
case STORAGE_POINTER:
case STORAGE_NULL:
+ case STORAGE_RUNE:
case STORAGE_SLICE:
case STORAGE_STRUCT:
case STORAGE_TAGGED:
diff --git a/src/parse.c b/src/parse.c
@@ -785,7 +785,7 @@ parse_constant(struct lexer *lexer)
case STORAGE_FCONST:
exp->constant.fval = tok.fval;
break;
- case STORAGE_RUNE:
+ case STORAGE_RCONST:
exp->constant.rune = tok.rune;
break;
case STORAGE_STRING:
@@ -812,6 +812,7 @@ parse_constant(struct lexer *lexer)
case STORAGE_ENUM:
case STORAGE_FUNCTION:
case STORAGE_POINTER:
+ case STORAGE_RUNE:
case STORAGE_SLICE:
case STORAGE_STRUCT:
case STORAGE_TAGGED:
diff --git a/src/qinstr.c b/src/qinstr.c
@@ -37,6 +37,7 @@ store_for_type(struct gen_context *ctx, const struct type *type)
case STORAGE_U32:
case STORAGE_INT:
case STORAGE_UINT:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_BOOL:
return Q_STOREW;
@@ -101,6 +102,7 @@ load_for_type(struct gen_context *ctx, const struct type *type)
return Q_LOADUH;
case STORAGE_U32:
case STORAGE_UINT:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_BOOL:
return Q_LOADUW;
diff --git a/src/qtype.c b/src/qtype.c
@@ -166,6 +166,7 @@ aggregate_lookup(struct gen_context *ctx, const struct type *type)
case STORAGE_BOOL:
case STORAGE_I32:
case STORAGE_U32:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_INT:
case STORAGE_UINT:
@@ -237,9 +238,11 @@ qtype_lookup(struct gen_context *ctx,
case STORAGE_FUNCTION:
return ctx->arch.ptr;
case STORAGE_VOID:
+ abort(); // Invariant
case STORAGE_FCONST:
case STORAGE_ICONST:
- abort(); // Invariant
+ case STORAGE_RCONST:
+ return qtype_lookup(ctx, lower_const(type, NULL), xtype);
}
abort(); // Invariant
}
@@ -285,7 +288,9 @@ type_is_aggregate(const struct type *type)
return true;
case STORAGE_FCONST:
case STORAGE_ICONST:
- assert(0); // Lowered in check
+ case STORAGE_RCONST:
+ lower_const(type, NULL);
+ return false;
}
assert(0); // Unreachable
}
diff --git a/src/scope.c b/src/scope.c
@@ -97,6 +97,7 @@ scope_object_init(struct scope_object *object, enum object_type otype,
assert(otype == O_CONST);
assert(value->type == EXPR_CONSTANT);
}
+ const_refer(type, &object->type);
}
void
diff --git a/src/type_store.c b/src/type_store.c
@@ -79,8 +79,6 @@ builtin_type_for_storage(enum type_storage storage, bool is_const)
return is_const ? &builtin_type_const_f32 : &builtin_type_f32;
case STORAGE_F64:
return is_const ? &builtin_type_const_f64 : &builtin_type_f64;
- case STORAGE_FCONST:
- return is_const ? &builtin_type_const_fconst : &builtin_type_fconst;
case STORAGE_I8:
return is_const ? &builtin_type_const_i8 : &builtin_type_i8;
case STORAGE_I16:
@@ -89,8 +87,6 @@ builtin_type_for_storage(enum type_storage storage, bool is_const)
return is_const ? &builtin_type_const_i32 : &builtin_type_i32;
case STORAGE_I64:
return is_const ? &builtin_type_const_i64 : &builtin_type_i64;
- case STORAGE_ICONST:
- return is_const ? &builtin_type_const_iconst : &builtin_type_iconst;
case STORAGE_INT:
return is_const ? &builtin_type_const_int : &builtin_type_int;
case STORAGE_RUNE:
@@ -118,6 +114,9 @@ builtin_type_for_storage(enum type_storage storage, bool is_const)
case STORAGE_ALIAS:
case STORAGE_ARRAY:
case STORAGE_FUNCTION:
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
case STORAGE_POINTER:
case STORAGE_SLICE:
case STORAGE_STRUCT:
@@ -388,7 +387,7 @@ collect_tagged_memb(struct type_store *store,
}
struct type_tagged_union *tu;
ta[*i] = tu = xcalloc(1, sizeof(struct type_tagged_union));
- tu->type = type;
+ tu->type = lower_const(type, NULL);
*i += 1;
}
}
@@ -408,7 +407,7 @@ collect_atagged_memb(struct type_store *store,
}
struct type_tagged_union *tu;
ta[*i] = tu = xcalloc(1, sizeof(struct type_tagged_union));
- tu->type = type;
+ tu->type = lower_const(type, NULL);
*i += 1;
}
}
@@ -603,6 +602,7 @@ type_init_from_atype(struct type_store *store,
switch (type->storage) {
case STORAGE_FCONST:
case STORAGE_ICONST:
+ case STORAGE_RCONST:
assert(0); // Invariant
case STORAGE_BOOL:
case STORAGE_CHAR:
@@ -958,6 +958,7 @@ const struct type *
type_store_lookup_pointer(struct type_store *store,
const struct type *referent, unsigned int ptrflags)
{
+ referent = lower_const(referent, NULL);
struct type ptr = {
.storage = STORAGE_POINTER,
.pointer = {
@@ -974,6 +975,7 @@ const struct type *
type_store_lookup_array(struct type_store *store,
const struct type *members, size_t len, bool expandable)
{
+ members = lower_const(members, NULL);
struct type array = {
.storage = STORAGE_ARRAY,
.array = {
@@ -992,6 +994,7 @@ type_store_lookup_array(struct type_store *store,
const struct type *
type_store_lookup_slice(struct type_store *store, const struct type *members)
{
+ members = lower_const(members, NULL);
struct type slice = {
.storage = STORAGE_SLICE,
.array = {
@@ -1083,9 +1086,9 @@ type_store_lookup_tuple(struct type_store *store, struct type_tuple *values,
{
struct type type = {
.storage = STORAGE_TUPLE,
- .tuple = *values,
};
for (struct type_tuple *t = values; t; t = t->next) {
+ t->type = lower_const(t->type, NULL);
if (t->type->align > type.align) {
type.align = t->type->align;
}
@@ -1097,6 +1100,7 @@ type_store_lookup_tuple(struct type_store *store, struct type_tuple *values,
t->offset = type.size % t->type->align + type.size;
type.size += type.size % t->type->align + t->type->size;
}
+ type.tuple = *values;
return type_store_lookup_type(store, &type);
}
diff --git a/src/typedef.c b/src/typedef.c
@@ -85,7 +85,7 @@ emit_const(const struct expression *expr, FILE *out)
case STORAGE_ICONST:
case STORAGE_INT:
fprintf(out, "%ld%s", val->ival,
- storage_to_suffix(expr->result->storage));
+ storage_to_suffix(type_dealias(expr->result)->storage));
break;
case STORAGE_NULL:
fprintf(out, "null");
@@ -98,11 +98,12 @@ emit_const(const struct expression *expr, FILE *out)
case STORAGE_UINT:
case STORAGE_UINTPTR:
fprintf(out, "%lu%s", val->uval,
- storage_to_suffix(expr->result->storage));
+ storage_to_suffix(type_dealias(expr->result)->storage));
break;
case STORAGE_VOID:
fprintf(out, "void");
break;
+ case STORAGE_RCONST:
case STORAGE_RUNE:
fprintf(out, "\'\\U%08" PRIx32 "\'", (uint32_t)val->uval);
break;
@@ -208,12 +209,14 @@ emit_type(const struct type *type, FILE *out)
case STORAGE_CHAR:
case STORAGE_F32:
case STORAGE_F64:
+ case STORAGE_FCONST:
case STORAGE_I16:
case STORAGE_I32:
case STORAGE_I64:
case STORAGE_I8:
case STORAGE_INT:
case STORAGE_NULL:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_SIZE:
case STORAGE_STRING:
@@ -317,9 +320,10 @@ emit_type(const struct type *type, FILE *out)
}
fprintf(out, ")");
break;
- case STORAGE_FCONST:
case STORAGE_ICONST:
- assert(0); // Invariant
+ fprintf(out, "[iconst min=%jd max=%jd]", type->_const.min,
+ type->_const.max);
+ break;
}
return ret;
}
diff --git a/src/types.c b/src/types.c
@@ -2,6 +2,8 @@
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
+#include "expr.h"
+#include "scope.h"
#include "types.h"
#include "util.h"
@@ -136,6 +138,8 @@ type_storage_unparse(enum type_storage storage)
return "pointer";
case STORAGE_NULL:
return "null";
+ case STORAGE_RCONST:
+ return "rconst";
case STORAGE_RUNE:
return "rune";
case STORAGE_SIZE:
@@ -186,6 +190,7 @@ type_is_integer(const struct type *type)
case STORAGE_UNION:
case STORAGE_BOOL:
case STORAGE_NULL:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_F32:
case STORAGE_F64:
@@ -229,6 +234,7 @@ type_is_numeric(const struct type *type)
case STORAGE_UNION:
case STORAGE_BOOL:
case STORAGE_CHAR:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_NULL:
return false;
@@ -259,16 +265,20 @@ type_is_numeric(const struct type *type)
bool
type_is_float(const struct type *type)
{
+ type = type_dealias(type);
return type->storage == STORAGE_F32 || type->storage == STORAGE_F64
|| type->storage == STORAGE_FCONST;
}
bool
-type_storage_is_signed(enum type_storage storage)
+type_is_signed(const struct type *type)
{
+ enum type_storage storage = type_dealias(type)->storage;
+ if (storage == STORAGE_ENUM) {
+ storage = type_dealias(type)->_enum.storage;
+ }
switch (storage) {
case STORAGE_VOID:
- case STORAGE_ALIAS:
case STORAGE_ARRAY:
case STORAGE_ENUM:
case STORAGE_FUNCTION:
@@ -281,6 +291,7 @@ type_storage_is_signed(enum type_storage storage)
case STORAGE_UNION:
case STORAGE_BOOL:
case STORAGE_CHAR:
+ case STORAGE_RCONST:
case STORAGE_RUNE:
case STORAGE_NULL:
case STORAGE_SIZE:
@@ -301,59 +312,19 @@ type_storage_is_signed(enum type_storage storage)
case STORAGE_FCONST:
return true;
case STORAGE_ICONST:
- assert(0); // XXX
+ return type->_const.min < 0;
+ case STORAGE_ALIAS:
+ assert(0); // Handled above
}
assert(0); // Unreachable
}
bool
-type_is_signed(const struct type *type)
+type_is_constant(const struct type *type)
{
- if (type->storage == STORAGE_ENUM) {
- return type_storage_is_signed(type->_enum.storage);
- }
- return type_storage_is_signed(type_dealias(type)->storage);
-}
-
-bool storage_is_flexible(enum type_storage storage)
-{
- switch (storage) {
- case STORAGE_ALIAS:
- case STORAGE_ARRAY:
- case STORAGE_BOOL:
- case STORAGE_CHAR:
- case STORAGE_ENUM:
- case STORAGE_F32:
- case STORAGE_F64:
- case STORAGE_FUNCTION:
- case STORAGE_I16:
- case STORAGE_I32:
- case STORAGE_I64:
- case STORAGE_I8:
- case STORAGE_INT:
- case STORAGE_NULL:
- case STORAGE_POINTER:
- case STORAGE_RUNE:
- case STORAGE_SIZE:
- case STORAGE_SLICE:
- case STORAGE_STRING:
- case STORAGE_STRUCT:
- case STORAGE_TAGGED:
- case STORAGE_TUPLE:
- case STORAGE_U16:
- case STORAGE_U32:
- case STORAGE_U64:
- case STORAGE_U8:
- case STORAGE_UINT:
- case STORAGE_UINTPTR:
- case STORAGE_UNION:
- case STORAGE_VOID:
- return false;
- case STORAGE_FCONST:
- case STORAGE_ICONST:
- return true;
- }
- assert(0); // Unreachable
+ return type->storage == STORAGE_FCONST
+ || type->storage == STORAGE_ICONST
+ || type->storage == STORAGE_RCONST;
}
uint32_t
@@ -368,12 +339,10 @@ type_hash(const struct type *type)
case STORAGE_CHAR:
case STORAGE_F32:
case STORAGE_F64:
- case STORAGE_FCONST:
case STORAGE_I8:
case STORAGE_I16:
case STORAGE_I32:
case STORAGE_I64:
- case STORAGE_ICONST:
case STORAGE_INT:
case STORAGE_NULL:
case STORAGE_RUNE:
@@ -416,6 +385,11 @@ type_hash(const struct type *type)
hash = fnv1a_u64(hash, value->uval);
}
break;
+ case STORAGE_FCONST:
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ hash = fnv1a(hash, type->_const.id);
+ break;
case STORAGE_POINTER:
hash = fnv1a(hash, type->pointer.flags);
hash = fnv1a_u32(hash, type_hash(type->pointer.referent));
@@ -506,6 +480,193 @@ tagged_select_subtype(const struct type *tagged, const struct type *subtype)
return NULL;
}
+static intmax_t
+min_value(const struct type *t)
+{
+ assert(type_is_integer(t));
+ if (!type_is_signed(t)) {
+ return 0;
+ }
+ if (t->size == sizeof(intmax_t)) {
+ return INTMAX_MIN;
+ }
+ return -(1 << (t->size * 8 - 1));
+}
+
+static uintmax_t
+max_value(const struct type *t)
+{
+ assert(type_is_integer(t));
+ size_t bits = t->size * 8;
+ if (type_is_signed(t)) {
+ bits--;
+ }
+ if (bits == sizeof(uintmax_t) * 8) {
+ return UINTMAX_MAX;
+ }
+ return ((uintmax_t)1 << bits) - 1;
+}
+
+const struct type *
+type_create_const(enum type_storage storage, intmax_t min, intmax_t max)
+{
+ // XXX: This'll be impossible to free. The right solution would be to
+ // store iconsts in the type store, but that'd require passing the store
+ // into type_is_assignable et al. An easier solution would be to keep
+ // our own list of iconsts and free them separately. Whatever, it
+ // doesn't really matter that much.
+ static uint32_t id = 0;
+ struct type *type = xcalloc(sizeof(struct type), 1);
+ type->storage = storage;
+ type->size = SIZE_UNDEFINED;
+ type->align = ALIGN_UNDEFINED;
+ type->_const.min = min;
+ type->_const.max = max;
+ type->_const.id = id++;
+ type->id = type_hash(type);
+ assert(type_is_constant(type));
+ return type;
+}
+
+// Register a reference to a flexible constant type. When `type` is lowered in
+// [[lower_const]], *ref will be updated to point to the new type.
+void
+const_refer(const struct type *type, const struct type **ref)
+{
+ if (type == NULL || !type_is_constant(type)) {
+ return;
+ }
+ struct type_const *constant = (struct type_const *)&type->_const;
+
+ if (constant->nrefs >= constant->zrefs) {
+ constant->zrefs *= 2;
+ if (constant->zrefs == 0) {
+ constant->zrefs++;
+ }
+ constant->refs = xrealloc(constant->refs,
+ constant->zrefs * sizeof(const struct type **));
+ }
+ constant->refs[constant->nrefs] = ref;
+ constant->nrefs++;
+}
+
+// Lower a flexible constant type. If new == NULL, lower it to its default type.
+const struct type *
+lower_const(const struct type *old, const struct type *new) {
+ if (!type_is_constant(old)) {
+ // If new != NULL, we're expected to always do something, and we
+ // can't if it's not a constant
+ assert(new == NULL);
+ return old;
+ }
+ if (new == NULL) {
+ switch (old->storage) {
+ case STORAGE_FCONST:
+ new = &builtin_type_f64;
+ break;
+ case STORAGE_ICONST:
+ if (old->_const.max <= (intmax_t)max_value(&builtin_type_int)
+ && old->_const.min >= min_value(&builtin_type_int)) {
+ new = &builtin_type_int;
+ } else {
+ new = &builtin_type_i64;
+ }
+ break;
+ case STORAGE_RCONST:
+ new = &builtin_type_rune;
+ break;
+ default:
+ assert(0);
+ }
+ }
+ for (size_t i = 0; i < old->_const.nrefs; i++) {
+ const_refer(new, old->_const.refs[i]);
+ *old->_const.refs[i] = new;
+ }
+ // XXX: Can we free old?
+ return new;
+}
+
+// Implements the flexible constant promotion algorithm
+const struct type *
+promote_const(const struct type *a, const struct type *b) {
+ if (a->storage == STORAGE_ICONST && b->storage == STORAGE_ICONST) {
+ intmax_t min = a->_const.min < b->_const.min
+ ? a->_const.min : b->_const.min;
+ intmax_t max = a->_const.max > b->_const.max
+ ? a->_const.max : b->_const.max;
+ const struct type *l =
+ type_create_const(STORAGE_ICONST, min, max);
+ lower_const(a, l);
+ lower_const(b, l);
+ return l;
+ }
+ if (type_is_constant(a)) {
+ if (a->storage == b->storage) {
+ const struct type *l =
+ type_create_const(a->storage, 0, 0);
+ lower_const(a, l);
+ lower_const(b, l);
+ return l;
+ }
+ if (type_is_constant(b)) {
+ return NULL;
+ }
+ return promote_const(b, a);
+ }
+ assert(!type_is_constant(a) && type_is_constant(b));
+ if (type_dealias(a)->storage == STORAGE_TAGGED) {
+ const struct type *tag = NULL;
+ for (const struct type_tagged_union *tu =
+ &type_dealias(a)->tagged; tu; tu = tu->next) {
+ const struct type *p = promote_const(tu->type, b);
+ if (!p) {
+ continue;
+ }
+ if (tag) {
+ // Ambiguous
+ return NULL;
+ }
+ tag = p;
+ }
+ return tag;
+ }
+ switch (b->storage) {
+ case STORAGE_FCONST:
+ if (!type_is_float(a)) {
+ return NULL;
+ }
+ lower_const(b, a);
+ return a;
+ case STORAGE_ICONST:
+ if (!type_is_integer(a)) {
+ return NULL;
+ }
+ if (type_is_signed(a) && min_value(a) > b->_const.min) {
+ return NULL;
+ }
+ if (b->_const.max > 0 && max_value(a) < (uintmax_t)b->_const.max) {
+ return NULL;
+ }
+ lower_const(b, a);
+ return a;
+ case STORAGE_RCONST:
+ if (type_dealias(a)->storage == STORAGE_RUNE) {
+ lower_const(b, a);
+ return a;
+ }
+ // XXX: This is probably a bit too lenient but I can't think of
+ // a better way to do this
+ if (!type_is_integer(a)) {
+ return NULL;
+ }
+ lower_const(b, a);
+ return a;
+ default:
+ assert(0); // Invariant
+ }
+}
+
bool
tagged_subset_compat(const struct type *to, const struct type *from)
{
@@ -545,6 +706,7 @@ struct_subtype(const struct type *to, const struct type *from) {
}
return false;
}
+
bool
type_is_assignable(const struct type *to, const struct type *from)
{
@@ -555,18 +717,23 @@ type_is_assignable(const struct type *to, const struct type *from)
// const and non-const types are mutually assignable
struct type _to, _from;
- const struct type *from_orig = from;
+ const struct type *to_orig = to, *from_orig = from;
to = strip_flags(to, &_to), from = strip_flags(from, &_from);
if (to->id == from->id) {
return true;
}
+
+ if (type_is_constant(from)) {
+ return promote_const(to_orig, from_orig);
+ }
struct type _to_secondary, _from_secondary;
const struct type *to_secondary, *from_secondary;
switch (to->storage) {
- case STORAGE_ICONST:
case STORAGE_FCONST:
- assert(0); // Invariant
+ case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ return promote_const(to_orig, from_orig);
case STORAGE_I8:
case STORAGE_I16:
case STORAGE_I32:
@@ -732,6 +899,8 @@ type_is_castable(const struct type *to, const struct type *from)
switch (from->storage) {
case STORAGE_FCONST:
case STORAGE_ICONST:
+ case STORAGE_RCONST:
+ return lower_const(from, to);
case STORAGE_I8:
case STORAGE_I16:
case STORAGE_I32:
@@ -800,24 +969,22 @@ builtin_types_init()
{
struct type *builtins[] = {
&builtin_type_bool, &builtin_type_char, &builtin_type_f32,
- &builtin_type_f64, &builtin_type_fconst, &builtin_type_i8,
- &builtin_type_i16, &builtin_type_i32, &builtin_type_i64,
- &builtin_type_iconst, &builtin_type_int, &builtin_type_u8,
- &builtin_type_u16, &builtin_type_u32, &builtin_type_u64,
- &builtin_type_uint, &builtin_type_uintptr, &builtin_type_null,
- &builtin_type_rune, &builtin_type_size, &builtin_type_void,
- &builtin_type_const_bool, &builtin_type_const_char,
- &builtin_type_const_f32, &builtin_type_const_f64,
- &builtin_type_const_fconst, &builtin_type_const_i8,
+ &builtin_type_f64, &builtin_type_i8, &builtin_type_i16,
+ &builtin_type_i32, &builtin_type_i64, &builtin_type_int,
+ &builtin_type_u8, &builtin_type_u16, &builtin_type_u32,
+ &builtin_type_u64, &builtin_type_uint, &builtin_type_uintptr,
+ &builtin_type_null, &builtin_type_rune, &builtin_type_size,
+ &builtin_type_void, &builtin_type_const_bool,
+ &builtin_type_const_char, &builtin_type_const_f32,
+ &builtin_type_const_f64, &builtin_type_const_i8,
&builtin_type_const_i16, &builtin_type_const_i32,
- &builtin_type_const_i64, &builtin_type_const_iconst,
- &builtin_type_const_int, &builtin_type_const_u8,
- &builtin_type_const_u16, &builtin_type_const_u32,
- &builtin_type_const_u64, &builtin_type_const_uint,
- &builtin_type_const_uintptr, &builtin_type_const_rune,
- &builtin_type_const_size, &builtin_type_const_void,
- &builtin_type_ptr_const_char, &builtin_type_str,
- &builtin_type_const_str,
+ &builtin_type_const_i64, &builtin_type_const_int,
+ &builtin_type_const_u8, &builtin_type_const_u16,
+ &builtin_type_const_u32, &builtin_type_const_u64,
+ &builtin_type_const_uint, &builtin_type_const_uintptr,
+ &builtin_type_const_rune, &builtin_type_const_size,
+ &builtin_type_const_void, &builtin_type_ptr_const_char,
+ &builtin_type_str, &builtin_type_const_str,
};
for (size_t i = 0; i < sizeof(builtins) / sizeof(builtins[0]); ++i) {
builtins[i]->id = type_hash(builtins[i]);
@@ -845,11 +1012,6 @@ builtin_type_f64 = {
.size = 8,
.align = 8,
},
-builtin_type_fconst = {
- .storage = STORAGE_FCONST,
- .size = SIZE_UNDEFINED,
- .align = ALIGN_UNDEFINED,
-},
builtin_type_i8 = {
.storage = STORAGE_I8,
.size = 1,
@@ -870,11 +1032,6 @@ builtin_type_i64 = {
.size = 8,
.align = 8,
},
-builtin_type_iconst = {
- .storage = STORAGE_ICONST,
- .size = SIZE_UNDEFINED,
- .align = ALIGN_UNDEFINED,
-},
builtin_type_int = {
.storage = STORAGE_INT,
.size = 4, // XXX: ARCH
@@ -954,12 +1111,6 @@ builtin_type_const_f64 = {
.size = 8,
.align = 8,
},
-builtin_type_const_fconst = {
- .storage = STORAGE_FCONST,
- .flags = TYPE_CONST,
- .size = SIZE_UNDEFINED,
- .align = ALIGN_UNDEFINED,
-},
builtin_type_const_i8 = {
.storage = STORAGE_I8,
.flags = TYPE_CONST,
@@ -984,12 +1135,6 @@ builtin_type_const_i64 = {
.size = 8,
.align = 8,
},
-builtin_type_const_iconst = {
- .storage = STORAGE_ICONST,
- .flags = TYPE_CONST,
- .size = SIZE_UNDEFINED,
- .align = ALIGN_UNDEFINED,
-},
builtin_type_const_int = {
.storage = STORAGE_INT,
.flags = TYPE_CONST,