diff options
author | Michael Forney <mforney@mforney.org> | 2024-03-21 16:43:00 -0700 |
---|---|---|
committer | Michael Forney <mforney@mforney.org> | 2024-03-21 17:33:40 -0700 |
commit | d1d23429f5dc3d78b03b318a7d349ad71a6c97fa (patch) | |
tree | 072e44175ebd75970565e9eb3b893bada3bbdd2a | |
parent | 8bed97beaea3839369a947ea741aa083e76ca014 (diff) |
decl: Add support for enums with large values and fixed underlying types
Fixes: https://todo.sr.ht/~mcf/cproc/64
-rw-r--r-- | cc.h | 4 | ||||
-rw-r--r-- | decl.c | 121 | ||||
-rw-r--r-- | doc/c23.md | 22 | ||||
-rw-r--r-- | expr.c | 2 | ||||
-rw-r--r-- | test/enum-fixed.c | 36 | ||||
-rw-r--r-- | test/enum-fixed.qbe | 0 | ||||
-rw-r--r-- | test/enum-large-value.c | 68 | ||||
-rw-r--r-- | test/enum-large-value.qbe | 1 | ||||
-rw-r--r-- | type.c | 9 |
9 files changed, 223 insertions, 40 deletions
@@ -282,6 +282,9 @@ struct decl { /* the function might have an "inline definition" (C11 6.7.4p7) */ _Bool inlinedefn; } func; + struct { + struct decl *next; + } enumconst; enum builtinkind builtin; } u; }; @@ -431,6 +434,7 @@ struct type *typepromote(struct type *, unsigned); struct type *typeadjust(struct type *); enum typeprop typeprop(struct type *); struct member *typemember(struct type *, const char *, unsigned long long *); +_Bool typehasint(struct type *, unsigned long long, _Bool); struct param *mkparam(char *, struct type *, enum typequal); @@ -143,18 +143,25 @@ funcspec(enum funcspec *fs) } static void structdecl(struct scope *, struct structbuilder *); +static struct qualtype declspecs(struct scope *, enum storageclass *, enum funcspec *, int *); static struct type * tagspec(struct scope *s) { - struct type *t; + static struct type *const inttypes[][2] = { + {&typeuint, &typeint}, + {&typeulong, &typelong}, + {&typeullong, &typellong}, + }; + struct type *t, *et; char *tag, *name; enum typekind kind; - struct decl *d; + struct decl *d, *enumconsts; struct expr *e; struct structbuilder b; - unsigned long long i; - bool large; + unsigned long long value, max, min; + bool sign; + int i; switch (tok.kind) { case TSTRUCT: kind = TYPESTRUCT; break; @@ -163,22 +170,27 @@ tagspec(struct scope *s) default: fatal("internal error: unknown tag kind"); } next(); - if (tok.kind == TLBRACE) { - tag = NULL; - t = NULL; - } else { - tag = expect(TIDENT, "or '{' after struct/union"); - t = scopegettag(s, tag, false); - if (s->parent && !t && tok.kind != TLBRACE && (kind == TYPEENUM || tok.kind != TSEMICOLON)) - t = scopegettag(s->parent, tag, true); + tag = NULL; + t = NULL; + et = NULL; + if (tok.kind == TIDENT) { + tag = tok.lit; + next(); } + if (kind == TYPEENUM && consume(TCOLON)) { + et = declspecs(s, NULL, NULL, NULL).type; + if (!et) + error(&tok.loc, "no type in enum type specifier"); + } + if (tag) + t = scopegettag(s, tag, tok.kind != TLBRACE && tok.kind != TSEMICOLON); if (t) { if (t->kind != kind) error(&tok.loc, "redeclaration of tag '%s' with different kind", tag); } else { if (kind == TYPEENUM) { t = mktype(kind, PROPSCALAR|PROPARITH|PROPREAL|PROPINT); - t->base = &typeuint; + t->base = et; } else { t = mktype(kind, 0); t->size = 0; @@ -207,46 +219,85 @@ tagspec(struct scope *s) error(&tok.loc, "struct/union has no members"); next(); t->size = ALIGNUP(t->size, t->align); - t->incomplete = false; break; case TYPEENUM: - large = false; - for (i = 0; tok.kind == TIDENT; ++i) { + enumconsts = NULL; + if (et) { + t->size = t->base->size; + t->align = t->base->align; + t->u.basic.issigned = t->base->u.basic.issigned; + t->incomplete = false; + et = t; + } else { + et = &typeint; + } + max = 0; + min = 0; + for (value = 0; tok.kind == TIDENT; ++value) { name = tok.lit; next(); if (consume(TASSIGN)) { e = evalexpr(s); if (e->kind != EXPRCONST || !(e->type->prop & PROPINT)) error(&tok.loc, "expected integer constant expression"); - i = e->u.constant.u; - if (e->type->u.basic.issigned && i >= 1ull << 63) { - if (i < -1ull << 31) - goto invalid; - t->base = &typeint; - } else if (i >= 1ull << 32) { + value = e->u.constant.u; + if (!t->base) + et = typehasint(&typeint, value, e->type->u.basic.issigned) ? &typeint : e->type; + else if (!typehasint(et, value, e->type->u.basic.issigned)) goto invalid; + } else if (value == 0 && !et->u.basic.issigned || value == 1ull << 63 && et->u.basic.issigned) { + error(&tok.loc, "no %ssigned integer type can represent enumerator value", et->u.basic.issigned ? "" : "un"); + } else if (!typehasint(et, value, et->u.basic.issigned)) { + if (t->base) { + invalid: + /* fixed underlying type */ + error(&tok.loc, "enumerator '%s' value cannot be represented in underlying type", name); + } + sign = et->u.basic.issigned; + for (i = 0; i < LEN(inttypes); ++i) { + et = inttypes[i][sign]; + if (typehasint(et, value, sign)) + break; } - } else if (i == 1ull << 32) { - invalid: - error(&tok.loc, "enumerator '%s' value cannot be represented as 'int' or 'unsigned int'", name); + assert(i < LEN(inttypes)); } - d = mkdecl(DECLCONST, &typeint, QUALNONE, LINKNONE); - d->value = mkintconst(i); - if (i >= 1ull << 31 && i < 1ull << 63) { - large = true; - d->type = &typeuint; + d = mkdecl(DECLCONST, et, QUALNONE, LINKNONE); + d->value = mkintconst(value); + d->u.enumconst.next = enumconsts; + enumconsts = d; + if (et->u.basic.issigned && value >= 1ull << 63) { + if (-value > min) + min = -value; + } else if (value > max) { + max = value; } - if (large && t->base->u.basic.issigned) - error(&tok.loc, "neither 'int' nor 'unsigned' can represent all enumerator values"); scopeputdecl(s, name, d); if (!consume(TCOMMA)) break; } expect(TRBRACE, "to close enum specifier"); - t->size = t->base->size; - t->align = t->base->align; - t->incomplete = false; + if (!t->base) { + if (min <= 0x80000000 && max <= 0x7fffffff) { + t->base = min ? &typeint : &typeuint; + } else { + sign = min > 0; + for (i = 0; i < LEN(inttypes); ++i) { + et = inttypes[i][sign]; + if (typehasint(et, max, false) && typehasint(et, -min, true)) + break; + } + if (i == LEN(inttypes)) + error(&tok.loc, "no integer type can represent all enumerator values"); + t->base = et; + for (d = enumconsts; d; d = d->u.enumconst.next) + d->type = t; + } + t->size = t->base->size; + t->align = t->base->align; + t->u.basic.issigned = t->base->u.basic.issigned; + } } + t->incomplete = false; return t; } @@ -40,6 +40,28 @@ and hexadecimal, using syntax like `0b01101011`. C23 allows empty initializers to initialize an object as if it had static storage duration. +## [N3029]: Improved Normal Enumerations + +C23 allows enumerators outside the range of `int`. When an enum +type contains such an enumerator, its type during processing of the +enum is the type of the initializing expression, or the type of the +previous enumerator if there is no initializing expression. In the +latter case, if the type of the previous enumerator can't represent +the current value, an integer type with the same signedness capable +of representing the value is chosen. Outside of an enum containing +a large enumerator, the types of all enumerators are changed to the +the enum type. + +## [N3030]: Enhanced Enumerations + +C23 allows enum types with fixed underlying types using syntax like +`enum E : unsigned long`. These enum types are compatible with the +underlying type, and all enumerator constants have the enum type. + +Enum types with fixed underlying types are complete by the end of +the underlying type specifier, so they can be forward-declared and +used inside the enum. + [N2265]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2265.pdf [N2418]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2418.pdf [N2508]: http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2508.pdf @@ -397,7 +397,7 @@ inttype(unsigned long long val, bool decimal, char *end) step = i % 2 || decimal ? 2 : 1; for (; i < LEN(limits); i += step) { t = limits[i].type; - if (val <= 0xffffffffffffffffu >> (8 - t->size << 3) + t->u.basic.issigned) + if (typehasint(t, val, false)) return t; } error(&tok.loc, "no suitable type for constant '%s'", tok.lit); diff --git a/test/enum-fixed.c b/test/enum-fixed.c new file mode 100644 index 0000000..9ac85ca --- /dev/null +++ b/test/enum-fixed.c @@ -0,0 +1,36 @@ +enum E1 : short; +_Static_assert(__builtin_types_compatible_p(enum E1, short)); + +enum E2 : unsigned short { + A2 = 0x7fff, + B2, + A2type = __builtin_types_compatible_p(typeof(A2), unsigned short), + B2type = __builtin_types_compatible_p(typeof(B2), unsigned short), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A2), unsigned short)); +_Static_assert(A2type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B2), unsigned short)); +_Static_assert(B2type == 1); +_Static_assert(__builtin_types_compatible_p(enum E2, unsigned short)); + +enum E3 : long long { + A3, + B3, + A3type = __builtin_types_compatible_p(typeof(A3), long long), + B3type = __builtin_types_compatible_p(typeof(B3), long long), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A3), long long)); +_Static_assert(A3type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B3), long long)); +_Static_assert(B3type == 1); +_Static_assert(__builtin_types_compatible_p(enum E3, long long)); + +enum E4 : long long { + A4 = sizeof(enum E4), + A4type1 = __builtin_types_compatible_p(typeof(A4), enum E4), + A4type2 = !__builtin_types_compatible_p(typeof(A4), enum E3), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A4), enum E4)); +_Static_assert(A4type1 == 1); +_Static_assert(!__builtin_types_compatible_p(typeof(A4), enum E3)); +_Static_assert(A4type2 == 1); diff --git a/test/enum-fixed.qbe b/test/enum-fixed.qbe new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/test/enum-fixed.qbe diff --git a/test/enum-large-value.c b/test/enum-large-value.c index 3532062..075021c 100644 --- a/test/enum-large-value.c +++ b/test/enum-large-value.c @@ -1,4 +1,66 @@ -enum { - A = 0x80000000, +enum E1 { + A1 = 0x80000000, + B1 = 0x80000000ll, + A1type = __builtin_types_compatible_p(typeof(A1), unsigned), + B1type = __builtin_types_compatible_p(typeof(B1), long long), }; -int x = A < 0; +_Static_assert(__builtin_types_compatible_p(typeof(A1), unsigned)); +_Static_assert(A1type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B1), unsigned)); +_Static_assert(B1type == 1); +_Static_assert(__builtin_types_compatible_p(enum E1, unsigned)); +_Static_assert(!__builtin_types_compatible_p(enum E1, enum { A1_ = A1, B1_ = B1 })); + +enum E2 { + A2 = 0x80000000, + B2 = -1ll, + A2type = __builtin_types_compatible_p(typeof(A2), unsigned), + B2type = __builtin_types_compatible_p(typeof(B2), int), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A2), long)); +_Static_assert(A2type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B2), long)); +_Static_assert(B2type == 1); +_Static_assert(__builtin_types_compatible_p(enum E2, long)); +_Static_assert(!__builtin_types_compatible_p(enum E2, enum { A2_ = A2, B2_ = B2 })); + +enum E3 { + A3 = 0x7fffffff, + B3, + A3type = __builtin_types_compatible_p(typeof(A3), int), + B3type = __builtin_types_compatible_p(typeof(B3), long), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A3), unsigned)); +_Static_assert(A3type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B3), unsigned)); +_Static_assert(B3type == 1); +_Static_assert(__builtin_types_compatible_p(enum E3, unsigned)); +_Static_assert(!__builtin_types_compatible_p(enum E3, enum { A3_ = A3, B3_ })); + +enum E4 { + A4 = -0x80000001l, + B4, + C4 = B4, + A4type = __builtin_types_compatible_p(typeof(A4), long), + B4type = __builtin_types_compatible_p(typeof(B4), long), + C4type = __builtin_types_compatible_p(typeof(C4), int), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A4), long)); +_Static_assert(A4type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B4), long)); +_Static_assert(B4type == 1); +_Static_assert(__builtin_types_compatible_p(enum E4, long)); +_Static_assert(!__builtin_types_compatible_p(enum E4, enum { A4_ = A4, B4_, C4 = C4 })); + +enum E5 { + A5 = 0x100000000, + B5 = -1ull, + A5type = __builtin_types_compatible_p(typeof(A5), long), + B5type = __builtin_types_compatible_p(typeof(B5), unsigned long long), +}; +_Static_assert(__builtin_types_compatible_p(typeof(A5), unsigned long)); +_Static_assert(A5type == 1); +_Static_assert(__builtin_types_compatible_p(typeof(B5), unsigned long)); +_Static_assert(B5type == 1); +_Static_assert(__builtin_types_compatible_p(enum E5, unsigned long)); +_Static_assert(!__builtin_types_compatible_p(enum E5, enum { A5_ = A5, B5_ = B5 })); diff --git a/test/enum-large-value.qbe b/test/enum-large-value.qbe index a67781d..e69de29 100644 --- a/test/enum-large-value.qbe +++ b/test/enum-large-value.qbe @@ -1 +0,0 @@ -export data $x = align 4 { w 0, } @@ -261,6 +261,15 @@ typemember(struct type *t, const char *name, unsigned long long *offset) return NULL; } +bool +typehasint(struct type *t, unsigned long long i, bool sign) +{ + assert(t->prop & PROPINT); + if (sign && i >= -1ull << 63) + return t->u.basic.issigned && i >= -1ull << (t->size << 3) - 1; + return i <= 0xffffffffffffffffull >> (8 - t->size << 3) + t->u.basic.issigned; +} + struct param * mkparam(char *name, struct type *t, enum typequal tq) { |