harec

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

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:
Minclude/types.h | 22+++++++++++++++++-----
Msrc/check.c | 259+++++++++++++++++--------------------------------------------------------------
Msrc/eval.c | 9++++++---
Msrc/gen.c | 6+++++-
Msrc/lex.c | 5+++--
Msrc/parse.c | 3++-
Msrc/qinstr.c | 2++
Msrc/qtype.c | 9+++++++--
Msrc/scope.c | 1+
Msrc/type_store.c | 18+++++++++++-------
Msrc/typedef.c | 12++++++++----
Msrc/types.c | 331+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
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,