aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Forney <mforney@mforney.org>2024-03-21 16:43:00 -0700
committerMichael Forney <mforney@mforney.org>2024-03-21 17:33:40 -0700
commitd1d23429f5dc3d78b03b318a7d349ad71a6c97fa (patch)
tree072e44175ebd75970565e9eb3b893bada3bbdd2a
parent8bed97beaea3839369a947ea741aa083e76ca014 (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.h4
-rw-r--r--decl.c121
-rw-r--r--doc/c23.md22
-rw-r--r--expr.c2
-rw-r--r--test/enum-fixed.c36
-rw-r--r--test/enum-fixed.qbe0
-rw-r--r--test/enum-large-value.c68
-rw-r--r--test/enum-large-value.qbe1
-rw-r--r--type.c9
9 files changed, 223 insertions, 40 deletions
diff --git a/cc.h b/cc.h
index 956e2e9..51b7cdc 100644
--- a/cc.h
+++ b/cc.h
@@ -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);
diff --git a/decl.c b/decl.c
index 37498d8..a0da3a9 100644
--- a/decl.c
+++ b/decl.c
@@ -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;
}
diff --git a/doc/c23.md b/doc/c23.md
index f612677..6dfee61 100644
--- a/doc/c23.md
+++ b/doc/c23.md
@@ -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
diff --git a/expr.c b/expr.c
index 4ad7f9d..1e94f68 100644
--- a/expr.c
+++ b/expr.c
@@ -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, }
diff --git a/type.c b/type.c
index ea9e56e..02e9a82 100644
--- a/type.c
+++ b/type.c
@@ -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)
{