aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNihal Jere <nihal@nihaljere.xyz>2024-04-27 14:47:15 -0700
committerMichael Forney <mforney@mforney.org>2024-04-27 15:34:03 -0700
commit4f206ac1ea1b20400fa242f2f3be86237c4ba3bf (patch)
tree2dc2742c051e6601874fc699265da40fd51bd5e5
parent487079af3d6f39b379f52f7e0ea6edec63587e5b (diff)
Implement variable length arrays
Variably modified types are required for C23. Since QBE doesn't currently support saving and restoring the stack pointer, a current limitation is that we can't reclaim stack space from VLAs that go out of scope. This is potentially problematic for VLAs appearing in a loop, but this case is uncommon enough that it is silently ignored for now. Implements: https://todo.sr.ht/~mcf/cproc/1 References: https://todo.sr.ht/~mcf/cproc/88 Co-authored-by: Michael Forney <mforney@mforney.org>
-rw-r--r--cc.h11
-rwxr-xr-xconfigure1
-rw-r--r--decl.c50
-rw-r--r--expr.c32
-rw-r--r--init.c8
-rw-r--r--qbe.c198
-rw-r--r--test/alignas-vla-strict.c5
-rw-r--r--test/alignas-vla-strict.qbe16
-rw-r--r--test/builtin-vaarg-vm.c16
-rw-r--r--test/builtin-vaarg-vm.qbe37
-rw-r--r--test/cast-vm.c6
-rw-r--r--test/cast-vm.qbe22
-rw-r--r--test/compatible-vla-types.c17
-rw-r--r--test/compatible-vla-types.qbe11
-rw-r--r--test/func-vla.c8
-rw-r--r--test/func-vla.qbe24
-rw-r--r--test/sizeof-vla.c19
-rw-r--r--test/sizeof-vla.qbe97
-rw-r--r--test/typeof-vm.c49
-rw-r--r--test/typeof-vm.qbe148
-rw-r--r--test/vla-deref.c4
-rw-r--r--test/vla-deref.qbe12
-rw-r--r--test/vla-nested.c13
-rw-r--r--test/vla-nested.qbe44
-rw-r--r--test/vla.c5
-rw-r--r--test/vla.qbe18
-rw-r--r--type.c9
27 files changed, 770 insertions, 110 deletions
diff --git a/cc.h b/cc.h
index ea237c5..ce0d6ca 100644
--- a/cc.h
+++ b/cc.h
@@ -178,7 +178,8 @@ enum typeprop {
PROPREAL = 1<<2,
PROPARITH = 1<<3,
PROPSCALAR = 1<<4,
- PROPFLOAT = 1<<5
+ PROPFLOAT = 1<<5,
+ PROPVM = 1<<6 /* variably-modified type */
};
struct bitfield {
@@ -213,6 +214,7 @@ struct type {
struct {
struct expr *length;
enum typequal ptrqual;
+ struct value *size;
} array;
struct {
bool isvararg;
@@ -321,6 +323,7 @@ enum exprkind {
EXPRBUILTIN,
EXPRTEMP,
+ EXPRSIZEOF,
};
struct stringlit {
@@ -341,6 +344,7 @@ struct expr {
enum tokenkind op;
struct expr *base;
struct expr *next;
+ struct expr *toeval;
union {
struct {
struct decl *decl;
@@ -377,6 +381,9 @@ struct expr {
struct {
enum builtinkind kind;
} builtin;
+ struct {
+ struct type *type;
+ } szof;
struct value *temp;
} u;
};
@@ -482,7 +489,7 @@ bool gnuattr(struct attr *, enum attrkind);
struct decl *mkdecl(char *name, enum declkind, struct type *, enum typequal, enum linkage);
bool decl(struct scope *, struct func *);
-struct type *typename(struct scope *, enum typequal *);
+struct type *typename(struct scope *, enum typequal *, struct expr **);
struct decl *stringdecl(struct expr *);
diff --git a/configure b/configure
index 7d2de5c..dab1bf3 100755
--- a/configure
+++ b/configure
@@ -149,7 +149,6 @@ static const char *const preprocesscmd[] = {
/* we don't yet support these optional features */
"-D", "__STDC_NO_ATOMICS__",
"-D", "__STDC_NO_COMPLEX__",
- "-D", "__STDC_NO_VLA__",
"-U", "__SIZEOF_INT128__",
/* we don't generate position-independent code */
diff --git a/decl.c b/decl.c
index 39c272d..4e148ac 100644
--- a/decl.c
+++ b/decl.c
@@ -13,6 +13,7 @@ static struct decl *tentativedefns, **tentativedefnsend = &tentativedefns;
struct qualtype {
struct type *type;
enum typequal qual;
+ struct expr *expr;
};
enum storageclass {
@@ -325,6 +326,7 @@ declspecs(struct scope *s, enum storageclass *sc, enum funcspec *fs, int *align)
enum tokenkind op;
int ntypes = 0;
unsigned long long i;
+ struct expr *typeofexpr = NULL;
t = NULL;
if (sc)
@@ -422,7 +424,7 @@ declspecs(struct scope *s, enum storageclass *sc, enum funcspec *fs, int *align)
case TTYPEOF_UNQUAL:
next();
expect(TLPAREN, "after 'typeof'");
- t = typename(s, &tq);
+ t = typename(s, &tq, &typeofexpr);
if (!t) {
e = expr(s);
if (e->decayed)
@@ -430,7 +432,8 @@ declspecs(struct scope *s, enum storageclass *sc, enum funcspec *fs, int *align)
t = e->type;
if (op == TTYPEOF)
tq |= e->qual;
- delexpr(e);
+ if (t->prop & PROPVM)
+ typeofexpr = e;
}
++ntypes;
expect(TRPAREN, "to close 'typeof'");
@@ -442,7 +445,7 @@ declspecs(struct scope *s, enum storageclass *sc, enum funcspec *fs, int *align)
error(&tok.loc, "alignment specifier not allowed in this declaration");
next();
expect(TLPAREN, "after 'alignas'");
- other = typename(s, NULL);
+ other = typename(s, NULL, NULL);
i = other ? other->align : intconstexpr(s, false);
if (i & i - 1 || i > INT_MAX)
error(&tok.loc, "invalid alignment: %llu", i);
@@ -505,7 +508,7 @@ done:
*/
attr(NULL, 0);
- return (struct qualtype){t, tq};
+ return (struct qualtype){t, tq, typeofexpr};
}
/* 6.7.6 Declarators */
@@ -630,16 +633,17 @@ declaratortypes(struct scope *s, struct list *result, char **name, struct scope
t = mkarraytype(NULL, QUALNONE, 0);
while (consume(TSTATIC) || typequal(&t->u.array.ptrqual))
;
- if (tok.kind == TMUL)
- error(&tok.loc, "VLAs are not yet supported");
- if (tok.kind != TRBRACK) {
+ if (tok.kind == TMUL && peek(TRBRACK)) {
+ t->prop |= PROPVM;
+ t->incomplete = false;
+ } else if (!consume(TRBRACK)) {
e = assignexpr(s);
if (!(e->type->prop & PROPINT))
error(&tok.loc, "array length expression must have integer type");
t->u.array.length = e;
t->incomplete = false;
+ expect(TRBRACK, "after array length");
}
- expect(TRBRACK, "after array length");
listinsert(ptr->prev, &t->link);
allowattr = true;
break;
@@ -673,6 +677,7 @@ declarator(struct scope *s, struct qualtype base, char **name, struct scope **fu
tq = t->qual;
t->base = base.type;
t->qual = base.qual;
+ t->prop |= base.type->prop & PROPVM;
switch (t->kind) {
case TYPEFUNC:
if (base.type->kind == TYPEFUNC)
@@ -689,13 +694,17 @@ declarator(struct scope *s, struct qualtype base, char **name, struct scope **fu
t->size = 0;
if (t->u.array.length) {
e = eval(t->u.array.length);
- if (e->kind != EXPRCONST)
- error(&tok.loc, "VLAs are not yet supported");
- if (e->type->u.basic.issigned && e->u.constant.u >> 63)
- error(&tok.loc, "array length must be non-negative");
- if (e->u.constant.u > ULLONG_MAX / base.type->size)
- error(&tok.loc, "array length is too large");
- t->size = base.type->size * e->u.constant.u;
+ if (e->kind == EXPRCONST) {
+ if (e->type->u.basic.issigned && e->u.constant.u >> 63)
+ error(&tok.loc, "array length must be non-negative");
+ if (e->u.constant.u > ULLONG_MAX / base.type->size)
+ error(&tok.loc, "array length is too large");
+ t->size = base.type->size * e->u.constant.u;
+ } else {
+ t->prop |= PROPVM;
+ t->u.array.length = e;
+ t->u.array.size = NULL;
+ }
}
break;
}
@@ -743,6 +752,8 @@ addmember(struct structbuilder *b, struct qualtype mt, char *name, int align, un
}
if (mt.type->kind == TYPEFUNC)
error(&tok.loc, "struct member '%s' has function type", name);
+ if (mt.type->prop & PROPVM)
+ error(&tok.loc, "struct member '%s' has variably modified type", name);
if (mt.type->flexible)
error(&tok.loc, "struct member '%s' contains flexible array member", name);
assert(mt.type->align > 0);
@@ -875,7 +886,7 @@ structdecl(struct scope *s, struct structbuilder *b)
/* 6.7.7 Type names */
struct type *
-typename(struct scope *s, enum typequal *tq)
+typename(struct scope *s, enum typequal *tq, struct expr **toeval)
{
struct qualtype t;
@@ -884,6 +895,8 @@ typename(struct scope *s, enum typequal *tq)
t = declarator(s, t, NULL, NULL, true);
if (tq)
*tq |= t.qual;
+ if (toeval)
+ *toeval = t.expr;
}
return t.type;
}
@@ -1020,8 +1033,13 @@ decl(struct scope *s, struct func *f)
d->u.obj.storage = SDAUTO;
} else {
d->u.obj.storage = sc & SCTHREADLOCAL ? SDTHREAD : SDSTATIC;
+ if (t->prop & PROPVM)
+ error(&tok.loc, "object '%s' with %s storage duration cannot have variably modified type", name, d->u.obj.storage == SDSTATIC ? "static" : "thread");
d->value = mkglobal(d);
}
+
+ if (base.expr)
+ funcexpr(f, base.expr);
init = NULL;
hasinit = false;
if (consume(TASSIGN)) {
diff --git a/expr.c b/expr.c
index 7e7e649..aef6fe9 100644
--- a/expr.c
+++ b/expr.c
@@ -22,6 +22,7 @@ mkexpr(enum exprkind k, struct type *t, struct expr *b)
e->kind = k;
e->base = b;
e->next = NULL;
+ e->toeval = NULL;
return e;
}
@@ -602,13 +603,15 @@ generic(struct scope *s)
def = assignexpr(s);
} else {
qual = QUALNONE;
- t = typename(s, &qual);
+ t = typename(s, &qual, NULL);
if (!t)
error(&tok.loc, "expected typename for generic association");
if (t->kind == TYPEFUNC)
error(&tok.loc, "generic association must have object type");
if (t->incomplete)
error(&tok.loc, "generic association must have complete type");
+ if (t->prop & PROPVM)
+ error(&tok.loc, "generic association has variably modified type");
expect(TCOLON, "after type name");
e = assignexpr(s);
if (typecompatible(t, want) && qual == QUALNONE) {
@@ -780,7 +783,7 @@ designator(struct scope *s, struct type *t, unsigned long long *offset)
static struct expr *
builtinfunc(struct scope *s, enum builtinkind kind)
{
- struct expr *e;
+ struct expr *e, *toeval;
struct type *t;
struct member *m;
char *name;
@@ -816,7 +819,7 @@ builtinfunc(struct scope *s, enum builtinkind kind)
e->u.constant.f = strtod("nan", NULL);
break;
case BUILTINOFFSETOF:
- t = typename(s, NULL);
+ t = typename(s, NULL, NULL);
expect(TCOMMA, "after type name");
name = expect(TIDENT, "after ','");
if (t->kind != TYPESTRUCT && t->kind != TYPEUNION)
@@ -830,9 +833,9 @@ builtinfunc(struct scope *s, enum builtinkind kind)
free(name);
break;
case BUILTINTYPESCOMPATIBLEP:
- t = typename(s, NULL);
+ t = typename(s, NULL, NULL);
expect(TCOMMA, "after type name");
- e = mkconstexpr(&typeint, typecompatible(t, typename(s, NULL)));
+ e = mkconstexpr(&typeint, typecompatible(t, typename(s, NULL, NULL)));
break;
case BUILTINUNREACHABLE:
e = mkexpr(EXPRBUILTIN, &typevoid, NULL);
@@ -846,7 +849,8 @@ builtinfunc(struct scope *s, enum builtinkind kind)
if (typeadjvalist == targ->typevalist)
e->base = mkunaryexpr(TBAND, e->base);
expect(TCOMMA, "after va_list");
- e->type = typename(s, &e->qual);
+ e->type = typename(s, &e->qual, &toeval);
+ e->toeval = toeval;
break;
case BUILTINVACOPY:
e = mkexpr(EXPRASSIGN, &typevoid, NULL);
@@ -1071,12 +1075,13 @@ unaryexpr(struct scope *s)
case TALIGNOF:
next();
if (consume(TLPAREN)) {
- t = typename(s, NULL);
+ t = typename(s, NULL, NULL);
if (t) {
expect(TRPAREN, "after type name");
/* might be part of a compound literal */
if (op == TSIZEOF && tok.kind == TLBRACE)
parseinit(s, t);
+ e = NULL;
} else {
e = expr(s);
expect(TRPAREN, "after expression");
@@ -1101,7 +1106,12 @@ unaryexpr(struct scope *s)
error(&tok.loc, "%s operator applied to incomplete type", tokstr[op]);
if (t->kind == TYPEFUNC)
error(&tok.loc, "%s operator applied to function type", tokstr[op]);
- e = mkconstexpr(&typeulong, op == TSIZEOF ? t->size : t->align);
+ if (t->kind == TYPEARRAY && t->size == 0 && op == TSIZEOF) {
+ e = mkexpr(EXPRSIZEOF, &typeulong, e);
+ e->u.szof.type = e ? t : e->base->type;
+ } else {
+ e = mkconstexpr(&typeulong, op == TSIZEOF ? t->size : t->align);
+ }
break;
default:
e = postfixexpr(s, NULL);
@@ -1116,13 +1126,13 @@ castexpr(struct scope *s)
struct type *t, *ct;
struct decl *d;
enum typequal tq;
- struct expr *r, *e, **end;
+ struct expr *r, *e, **end, *toeval;
ct = NULL;
end = &r;
while (consume(TLPAREN)) {
tq = QUALNONE;
- t = typename(s, &tq);
+ t = typename(s, &tq, &toeval);
if (!t) {
e = expr(s);
expect(TRPAREN, "after expression to match '('");
@@ -1132,6 +1142,7 @@ castexpr(struct scope *s)
expect(TRPAREN, "after type name");
if (tok.kind == TLBRACE) {
e = mkexpr(EXPRCOMPOUND, t, NULL);
+ e->toeval = toeval;
e->qual = tq;
e->lvalue = true;
d = mkdecl(NULL, DECLOBJECT, t, tq, LINKNONE);
@@ -1144,6 +1155,7 @@ castexpr(struct scope *s)
if (t != &typevoid && !(t->prop & PROPSCALAR))
error(&tok.loc, "cast type must be scalar");
e = mkexpr(EXPRCAST, t, NULL);
+ e->toeval = toeval;
*end = e;
end = &e->base;
ct = t;
diff --git a/init.c b/init.c
index 1a7f5cd..c8f4128 100644
--- a/init.c
+++ b/init.c
@@ -206,8 +206,12 @@ parseinit(struct scope *s, struct type *t)
p.sub->iscur = false;
p.init = NULL;
p.last = &p.init;
- if (t->incomplete && t->kind != TYPEARRAY)
- error(&tok.loc, "initializer specified for incomplete type");
+ if (t->incomplete) {
+ if (t->kind != TYPEARRAY)
+ error(&tok.loc, "initializer specified for incomplete type");
+ } else if (t->kind == TYPEARRAY && t->size == 0) {
+ error(&tok.loc, "initializer specified for variable length array type");
+ }
for (;;) {
if (p.cur) {
if (tok.kind == TLBRACK || tok.kind == TPERIOD)
diff --git a/qbe.c b/qbe.c
index c3424c5..1cb87c8 100644
--- a/qbe.c
+++ b/qbe.c
@@ -262,6 +262,102 @@ funcinst(struct func *f, int op, int class, struct value *arg0, struct value *ar
return &inst->res;
}
+static struct value *
+convert(struct func *f, struct type *dst, struct type *src, struct value *l)
+{
+ enum instkind op;
+ struct value *r = NULL;
+ int class;
+
+ if (src->kind == TYPEPOINTER)
+ src = &typeulong;
+ if (dst->kind == TYPEPOINTER)
+ dst = &typeulong;
+ if (dst->kind == TYPEVOID)
+ return NULL;
+ if (!(src->prop & PROPREAL) || !(dst->prop & PROPREAL))
+ fatal("internal error; unsupported conversion");
+ if (dst->kind == TYPEBOOL) {
+ class = 'w';
+ if (src->prop & PROPINT) {
+ r = mkintconst(0);
+ switch (src->size) {
+ case 1: op = ICNEW, l = funcinst(f, IEXTUB, 'w', l, NULL); break;
+ case 2: op = ICNEW, l = funcinst(f, IEXTUH, 'w', l, NULL); break;
+ case 4: op = ICNEW; break;
+ case 8: op = ICNEL; break;
+ default:
+ fatal("internal error; unknown integer conversion");
+ return NULL; /* unreachable */
+ }
+ } else {
+ assert(src->prop & PROPFLOAT);
+ switch (src->size) {
+ case 4: op = ICNES, r = mkfltconst(VALUE_FLTCONST, 0); break;
+ case 8: op = ICNED, r = mkfltconst(VALUE_DBLCONST, 0); break;
+ default:
+ fatal("internal error; unknown floating point conversion");
+ return NULL; /* unreachable */
+ }
+ }
+ } else if (dst->prop & PROPINT) {
+ class = dst->size == 8 ? 'l' : 'w';
+ if (src->prop & PROPINT) {
+ if (dst->size <= src->size)
+ return l;
+ switch (src->size) {
+ case 4: op = src->u.basic.issigned ? IEXTSW : IEXTUW; break;
+ case 2: op = src->u.basic.issigned ? IEXTSH : IEXTUH; break;
+ case 1: op = src->u.basic.issigned ? IEXTSB : IEXTUB; break;
+ default:
+ fatal("internal error; unknown integer conversion");
+ return NULL; /* unreachable */
+ }
+ } else {
+ if (dst->u.basic.issigned)
+ op = src->size == 8 ? IDTOSI : ISTOSI;
+ else
+ op = src->size == 8 ? IDTOUI : ISTOUI;
+ }
+ } else {
+ class = dst->size == 8 ? 'd' : 's';
+ if (src->prop & PROPINT) {
+ if (src->u.basic.issigned)
+ op = src->size == 8 ? ISLTOF : ISWTOF;
+ else
+ op = src->size == 8 ? IULTOF : IUWTOF;
+ } else {
+ assert(src->prop & PROPFLOAT);
+ if (src->size == dst->size)
+ return l;
+ op = src->size < dst->size ? IEXTS : ITRUNCD;
+ }
+ }
+
+ return funcinst(f, op, class, l, r);
+}
+
+static void
+calcvla(struct func *f, struct type *t)
+{
+ struct value *length, *basesize;
+
+ if (!(t->prop & PROPVM))
+ return;
+ if (t->base)
+ calcvla(f, t->base);
+ if (t->kind == TYPEFUNC || t->size)
+ return;
+ assert(t->kind == TYPEARRAY);
+ if (!t->u.array.size) {
+ assert(t->base->size || t->base->kind == TYPEARRAY);
+ assert(t->u.array.length);
+ length = convert(f, &typeulong, t->u.array.length->type, funcexpr(f, t->u.array.length));
+ basesize = t->base->size ? mkintconst(t->base->size) : t->base->u.array.size;
+ t->u.array.size = funcinst(f, IMUL, 'l', length, basesize);
+ }
+}
+
static void
funcalloc(struct func *f, struct decl *d)
{
@@ -271,10 +367,16 @@ funcalloc(struct func *f, struct decl *d)
int align;
assert(!d->type->incomplete);
- assert(d->type->size > 0);
+ calcvla(f, d->type);
end = f->end;
- f->end = f->start;
- v = mkintconst(d->type->size);
+ if (d->type->size) {
+ f->end = f->start;
+ v = mkintconst(d->type->size);
+ } else {
+ assert(d->type->kind == TYPEARRAY);
+ assert(d->type->u.array.size);
+ v = d->type->u.array.size;
+ }
align = d->u.obj.align;
switch (align) {
case 1:
@@ -404,81 +506,6 @@ funcload(struct func *f, struct type *t, struct lvalue lval)
return funcbits(f, t, v, lval.bits);
}
-static struct value *
-convert(struct func *f, struct type *dst, struct type *src, struct value *l)
-{
- enum instkind op;
- struct value *r = NULL;
- int class;
-
- if (src->kind == TYPEPOINTER)
- src = &typeulong;
- if (dst->kind == TYPEPOINTER)
- dst = &typeulong;
- if (dst->kind == TYPEVOID)
- return NULL;
- if (!(src->prop & PROPREAL) || !(dst->prop & PROPREAL))
- fatal("internal error; unsupported conversion");
- if (dst->kind == TYPEBOOL) {
- class = 'w';
- if (src->prop & PROPINT) {
- r = mkintconst(0);
- switch (src->size) {
- case 1: op = ICNEW, l = funcinst(f, IEXTUB, 'w', l, NULL); break;
- case 2: op = ICNEW, l = funcinst(f, IEXTUH, 'w', l, NULL); break;
- case 4: op = ICNEW; break;
- case 8: op = ICNEL; break;
- default:
- fatal("internal error; unknown integer conversion");
- return NULL; /* unreachable */
- }
- } else {
- assert(src->prop & PROPFLOAT);
- switch (src->size) {
- case 4: op = ICNES, r = mkfltconst(VALUE_FLTCONST, 0); break;
- case 8: op = ICNED, r = mkfltconst(VALUE_DBLCONST, 0); break;
- default:
- fatal("internal error; unknown floating point conversion");
- return NULL; /* unreachable */
- }
- }
- } else if (dst->prop & PROPINT) {
- class = dst->size == 8 ? 'l' : 'w';
- if (src->prop & PROPINT) {
- if (dst->size <= src->size)
- return l;
- switch (src->size) {
- case 4: op = src->u.basic.issigned ? IEXTSW : IEXTUW; break;
- case 2: op = src->u.basic.issigned ? IEXTSH : IEXTUH; break;
- case 1: op = src->u.basic.issigned ? IEXTSB : IEXTUB; break;
- default:
- fatal("internal error; unknown integer conversion");
- return NULL; /* unreachable */
- }
- } else {
- if (dst->u.basic.issigned)
- op = src->size == 8 ? IDTOSI : ISTOSI;
- else
- op = src->size == 8 ? IDTOUI : ISTOUI;
- }
- } else {
- class = dst->size == 8 ? 'd' : 's';
- if (src->prop & PROPINT) {
- if (src->u.basic.issigned)
- op = src->size == 8 ? ISLTOF : ISWTOF;
- else
- op = src->size == 8 ? IULTOF : IUWTOF;
- } else {
- assert(src->prop & PROPFLOAT);
- if (src->size == dst->size)
- return l;
- op = src->size < dst->size ? IEXTS : ITRUNCD;
- }
- }
-
- return funcinst(f, op, class, l, r);
-}
-
struct func *
mkfunc(struct decl *decl, char *name, struct type *t, struct scope *s)
{
@@ -655,6 +682,8 @@ funclval(struct func *f, struct expr *e)
lval.addr = d->value;
break;
case EXPRCOMPOUND:
+ if (e->toeval)
+ funcexpr(f, e->toeval);
d = e->u.compound.decl;
funcinit(f, d, e->u.compound.init, true);
lval.addr = d->value;
@@ -684,6 +713,7 @@ funcexpr(struct func *f, struct expr *e)
struct type *t, *functype;
size_t i;
+ calcvla(f, e->type);
switch (e->kind) {
case EXPRIDENT:
d = e->u.ident.decl;
@@ -759,6 +789,8 @@ funcexpr(struct func *f, struct expr *e)
fatal("internal error; unknown unary expression");
break;
case EXPRCAST:
+ if (e->toeval)
+ funcexpr(f, e->toeval);
l = funcexpr(f, e->base);
return convert(f, e->type, e->base->type, l);
case EXPRBINARY:
@@ -902,6 +934,8 @@ funcexpr(struct func *f, struct expr *e)
funcinst(f, IVASTART, 0, l, NULL);
break;
case BUILTINVAARG:
+ if (e->toeval)
+ funcexpr(f, e->toeval);
/* https://todo.sr.ht/~mcf/cproc/52 */
if (!(e->type->prop & PROPSCALAR))
error(&tok.loc, "va_arg with non-scalar type is not yet supported");
@@ -919,6 +953,14 @@ funcexpr(struct func *f, struct expr *e)
case EXPRTEMP:
assert(e->u.temp);
return e->u.temp;
+ case EXPRSIZEOF:
+ t = e->u.szof.type;
+ assert(t->kind == TYPEARRAY);
+ calcvla(f, t);
+ /* if the sizeof operand has VLA type, we must evaluate it */
+ if (e->base)
+ funcexpr(f, e->base);
+ return t->u.array.size;
}
fatal("unimplemented expression %d", e->kind);
return NULL;
diff --git a/test/alignas-vla-strict.c b/test/alignas-vla-strict.c
new file mode 100644
index 0000000..0f5a326
--- /dev/null
+++ b/test/alignas-vla-strict.c
@@ -0,0 +1,5 @@
+int n = 43;
+int main(void) {
+ char alignas(64) a[n];
+ return (unsigned long)a % 64;
+}
diff --git a/test/alignas-vla-strict.qbe b/test/alignas-vla-strict.qbe
new file mode 100644
index 0000000..8fdc595
--- /dev/null
+++ b/test/alignas-vla-strict.qbe
@@ -0,0 +1,16 @@
+export data $n = align 4 { w 43, }
+export
+function w $main() {
+@start.1
+@body.2
+ %.1 =w loadw $n
+ %.2 =l extsw %.1
+ %.3 =l mul %.2, 1
+ %.4 =l add %.3, 48
+ %.5 =l alloc16 %.4
+ %.6 =l add %.5, 48
+ %.7 =l and %.6, 18446744073709551552
+ %.8 =l extsw 64
+ %.9 =l urem %.7, %.8
+ ret %.9
+}
diff --git a/test/builtin-vaarg-vm.c b/test/builtin-vaarg-vm.c
new file mode 100644
index 0000000..aee7216
--- /dev/null
+++ b/test/builtin-vaarg-vm.c
@@ -0,0 +1,16 @@
+int f(int i, ...) {
+ int r, c = 0;
+ __builtin_va_list ap;
+
+ __builtin_va_start(ap, i);
+ r = **__builtin_va_arg(ap, int (*)[++i]);
+ __builtin_va_end(ap);
+ return r + i;
+}
+
+int main(void) {
+ int a[3];
+
+ a[0] = 123;
+ return f(3, &a) != 127;
+}
diff --git a/test/builtin-vaarg-vm.qbe b/test/builtin-vaarg-vm.qbe
new file mode 100644
index 0000000..814e3d0
--- /dev/null
+++ b/test/builtin-vaarg-vm.qbe
@@ -0,0 +1,37 @@
+export
+function w $f(w %.1, ...) {
+@start.1
+ %.2 =l alloc4 4
+ storew %.1, %.2
+ %.3 =l alloc4 4
+ %.4 =l alloc4 4
+ %.5 =l alloc8 24
+@body.2
+ storew 0, %.4
+ vastart %.5
+ %.6 =w loadw %.2
+ %.7 =w add %.6, 1
+ storew %.7, %.2
+ %.8 =l extsw %.7
+ %.9 =l mul %.8, 4
+ %.10 =l vaarg %.5
+ %.11 =w loadw %.10
+ storew %.11, %.3
+ %.12 =w loadw %.3
+ %.13 =w loadw %.2
+ %.14 =w add %.12, %.13
+ ret %.14
+}
+export
+function w $main() {
+@start.3
+ %.1 =l alloc4 12
+@body.4
+ %.2 =l extsw 0
+ %.3 =l mul %.2, 4
+ %.4 =l add %.1, %.3
+ storew 123, %.4
+ %.5 =w call $f(w 3, ..., l %.1)
+ %.6 =w cnew %.5, 127
+ ret %.6
+}
diff --git a/test/cast-vm.c b/test/cast-vm.c
new file mode 100644
index 0000000..6901e73
--- /dev/null
+++ b/test/cast-vm.c
@@ -0,0 +1,6 @@
+int main(void) {
+ int l = 0;
+ (int (*)[++l])0;
+ (int (*(*)(void))[++l])0;
+ return l != 2;
+}
diff --git a/test/cast-vm.qbe b/test/cast-vm.qbe
new file mode 100644
index 0000000..2ca3f7d
--- /dev/null
+++ b/test/cast-vm.qbe
@@ -0,0 +1,22 @@
+export
+function w $main() {
+@start.1
+ %.1 =l alloc4 4
+@body.2
+ storew 0, %.1
+ %.2 =w loadw %.1
+ %.3 =w add %.2, 1
+ storew %.3, %.1
+ %.4 =l extsw %.3
+ %.5 =l mul %.4, 4
+ %.6 =l extsw 0
+ %.7 =w loadw %.1
+ %.8 =w add %.7, 1
+ storew %.8, %.1
+ %.9 =l extsw %.8
+ %.10 =l mul %.9, 4
+ %.11 =l extsw 0
+ %.12 =w loadw %.1
+ %.13 =w cnew %.12, 2
+ ret %.13
+}
diff --git a/test/compatible-vla-types.c b/test/compatible-vla-types.c
new file mode 100644
index 0000000..2f72dc5
--- /dev/null
+++ b/test/compatible-vla-types.c
@@ -0,0 +1,17 @@
+void f1(int n, int (*a)[n], int (*b)[*], int (*c)[3],
+ struct {
+ int x;
+ static_assert(__builtin_types_compatible_p(typeof(a), typeof(b)));
+ static_assert(__builtin_types_compatible_p(typeof(a), int (*)[3]));
+ static_assert(__builtin_types_compatible_p(typeof(b), int (*)[3]));
+ static_assert(__builtin_types_compatible_p(typeof(a), int (*)[]));
+ static_assert(__builtin_types_compatible_p(typeof(b), int (*)[]));
+ } s);
+void f2(void) {
+ int n = 12, m = 6 * 2;
+ static_assert(__builtin_types_compatible_p(int [n], int [12]));
+ static_assert(__builtin_types_compatible_p(int [], int [n]));
+ static_assert(__builtin_types_compatible_p(int [n], int [m]));
+ static_assert(__builtin_types_compatible_p(int [2][n], int [1 + 1][n]));
+ static_assert(!__builtin_types_compatible_p(int [4][n], int [5][n]));
+}
diff --git a/test/compatible-vla-types.qbe b/test/compatible-vla-types.qbe
new file mode 100644
index 0000000..330cd85
--- /dev/null
+++ b/test/compatible-vla-types.qbe
@@ -0,0 +1,11 @@
+export
+function $f2() {
+@start.1
+ %.1 =l alloc4 4
+ %.2 =l alloc4 4
+@body.2
+ storew 12, %.1
+ %.3 =w mul 6, 2
+ storew %.3, %.2
+ ret
+}
diff --git a/test/func-vla.c b/test/func-vla.c
new file mode 100644
index 0000000..6718c05
--- /dev/null
+++ b/test/func-vla.c
@@ -0,0 +1,8 @@
+int f(int n, long long (*a)[n]) {
+ return sizeof *a;
+}
+
+int main(void) {
+ long long a[5];
+ return f(5, &a) != sizeof a;
+}
diff --git a/test/func-vla.qbe b/test/func-vla.qbe
new file mode 100644
index 0000000..ac3cb9b
--- /dev/null
+++ b/test/func-vla.qbe
@@ -0,0 +1,24 @@
+export
+function w $f(w %.1, l %.3) {
+@start.1
+ %.2 =l alloc4 4
+ storew %.1, %.2
+ %.4 =w loadw %.2
+ %.5 =l extsw %.4
+ %.6 =l mul %.5, 8
+ %.7 =l alloc8 8
+ storel %.3, %.7
+@body.2
+ %.8 =l loadl %.7
+ ret %.6
+}
+export
+function w $main() {
+@start.3
+ %.1 =l alloc8 40
+@body.4
+ %.2 =w call $f(w 5, l %.1)
+ %.3 =l extsw %.2
+ %.4 =w cnel %.3, 40
+ ret %.4
+}
diff --git a/test/sizeof-vla.c b/test/sizeof-vla.c
new file mode 100644
index 0000000..16d6fa1
--- /dev/null
+++ b/test/sizeof-vla.c
@@ -0,0 +1,19 @@
+int c = 0;
+int main(void) {
+ int r = 0;
+ int l = 2;
+ int (*p)[l] = 0;
+ r += sizeof(c++, p) != sizeof(int (*)[]); /* VM, but not VLA */
+ r += c != 0;
+ r += sizeof(*(c++, p)) != 2 * sizeof(int); /* VLA */
+ r += c != 1;
+ r += sizeof(c++, *p) != sizeof(int *); /* VLA decayed to pointer */
+ r += c != 1;
+ r += sizeof(int[++l]) != 3 * sizeof(int); /* VLA */
+ r += l != 3;
+ r += sizeof(int[++l][1]) != sizeof(int[4][1]); /* VLA */
+ r += l != 4;
+ r += sizeof(int[(c++, 5)]) != 5 * sizeof(int); /* VLA */
+ r += c != 2;
+ return r;
+}
diff --git a/test/sizeof-vla.qbe b/test/sizeof-vla.qbe
new file mode 100644
index 0000000..44acda9
--- /dev/null
+++ b/test/sizeof-vla.qbe
@@ -0,0 +1,97 @@
+export data $c = align 4 { w 0, }
+export
+function w $main() {
+@start.1
+ %.1 =l alloc4 4
+ %.2 =l alloc4 4
+ %.6 =l alloc8 8
+@body.2
+ storew 0, %.1
+ storew 2, %.2
+ %.3 =w loadw %.2
+ %.4 =l extsw %.3
+ %.5 =l mul %.4, 4
+ %.7 =l extsw 0
+ storel %.7, %.6
+ %.8 =w loadw %.1
+ %.9 =w cnel 8, 8
+ %.10 =w add %.8, %.9
+ storew %.10, %.1
+ %.11 =w loadw %.1
+ %.12 =w loadw $c
+ %.13 =w cnew %.12, 0
+ %.14 =w add %.11, %.13
+ storew %.14, %.1
+ %.15 =w loadw %.1
+ %.16 =w loadw $c
+ %.17 =w add %.16, 1
+ storew %.17, $c
+ %.18 =l loadl %.6
+ %.19 =l extsw 2
+ %.20 =l mul %.19, 4
+ %.21 =w cnel %.5, %.20
+ %.22 =w add %.15, %.21
+ storew %.22, %.1
+ %.23 =w loadw %.1
+ %.24 =w loadw $c
+ %.25 =w cnew %.24, 1
+ %.26 =w add %.23, %.25
+ storew %.26, %.1
+ %.27 =w loadw %.1
+ %.28 =w cnel 8, 8
+ %.29 =w add %.27, %.28
+ storew %.29, %.1
+ %.30 =w loadw %.1
+ %.31 =w loadw $c
+ %.32 =w cnew %.31, 1
+ %.33 =w add %.30, %.32
+ storew %.33, %.1
+ %.34 =w loadw %.1
+ %.35 =w loadw %.2
+ %.36 =w add %.35, 1
+ storew %.36, %.2
+ %.37 =l extsw %.36
+ %.38 =l mul %.37, 4
+ %.39 =l extsw 3
+ %.40 =l mul %.39, 4
+ %.41 =w cnel %.38, %.40
+ %.42 =w add %.34, %.41
+ storew %.42, %.1
+ %.43 =w loadw %.1
+ %.44 =w loadw %.2
+ %.45 =w cnew %.44, 3
+ %.46 =w add %.43, %.45
+ storew %.46, %.1
+ %.47 =w loadw %.1
+ %.48 =w loadw %.2
+ %.49 =w add %.48, 1
+ storew %.49, %.2
+ %.50 =l extsw %.49
+ %.51 =l mul %.50, 4
+ %.52 =w cnel %.51, 16
+ %.53 =w add %.47, %.52
+ storew %.53, %.1
+ %.54 =w loadw %.1
+ %.55 =w loadw %.2
+ %.56 =w cnew %.55, 4
+ %.57 =w add %.54, %.56
+ storew %.57, %.1
+ %.58 =w loadw %.1
+ %.59 =w loadw $c
+ %.60 =w add %.59, 1
+ storew %.60, $c
+ %.61 =l extsw 5
+ %.62 =l mul %.61, 4
+ %.63 =l extsw 5
+ %.64 =l mul %.63, 4
+ %.65 =w cnel %.62, %.64
+ %.66 =w add %.58, %.65
+ storew %.66, %.1
+ %.67 =w loadw %.1
+ %.68 =w loadw $c
+ %.69 =w cnew %.68, 2
+ %.70 =w add %.67, %.69
+ storew %.70, %.1
+ %.71 =w loadw %.1
+ ret %.71
+}
diff --git a/test/typeof-vm.c b/test/typeof-vm.c
new file mode 100644
index 0000000..694a087
--- /dev/null
+++ b/test/typeof-vm.c
@@ -0,0 +1,49 @@
+int a[3] = {12, 34, 56};
+int b[3] = {'a', 'b', 'c'};
+int c = 0;
+int f(void) {
+ ++c;
+ return 3;
+}
+int g(int n, ...) {
+ __builtin_va_list ap;
+ char (*p)[n] = 0;
+ int out = 1;
+
+ __builtin_va_start(ap, n);
+ __builtin_va_arg(ap, typeof(out--, p));
+ __builtin_va_end(ap);
+ return out;
+}
+int main(void) {
+ int r = 0;
+ int (*p)[f()] = 0;
+
+ r += c != 1;
+ typeof(c++, p) t1; /* VM; evaluated */
+ r += c != 2;
+ typeof(p, c++) t2; /* non-VM; not evaluated */
+ r += c != 2;
+ typeof(c++, **(p = &a)) t3; /* non-VM; not evaluated */
+ r += c != 2;
+ r += p != 0;
+ typeof(*(p = (c++, &a))) t4; /* VM, evaluated */
+ r += c != 3;
+ r += p != &a;
+ /*
+ while *(p = &b) has VM type, it is not the immediate operand
+ of typeof, so is converted from VLA to int pointer due to
+ the comma operator, so the typeof expression is not evaluated
+ */
+ typeof(c++, *(p = &b)) t5;
+ r += c != 3;
+ r += p != &a;
+ (typeof(c++, p))0;
+ r += c != 4;
+ (typeof(c++, p)){0};
+ r += c != 5;
+ r += g(3, p);
+ typeof(typeof(c++, p)) t6;
+ r += c != 6;
+ return r;
+}
diff --git a/test/typeof-vm.qbe b/test/typeof-vm.qbe
new file mode 100644
index 0000000..c75de99
--- /dev/null
+++ b/test/typeof-vm.qbe
@@ -0,0 +1,148 @@
+export data $a = align 4 { w 12, w 34, w 56, }
+export data $b = align 4 { w 97, w 98, w 99, }
+export data $c = align 4 { w 0, }
+export
+function w $f() {
+@start.1
+@body.2
+ %.1 =w loadw $c
+ %.2 =w add %.1, 1
+ storew %.2, $c
+ ret 3
+}
+export
+function w $g(w %.1, ...) {
+@start.3
+ %.2 =l alloc4 4
+ storew %.1, %.2
+ %.3 =l alloc8 24
+ %.7 =l alloc8 8
+ %.9 =l alloc4 4
+@body.4
+ %.4 =w loadw %.2
+ %.5 =l extsw %.4
+ %.6 =l mul %.5, 1
+ %.8 =l extsw 0
+ storel %.8, %.7
+ storew 1, %.9
+ vastart %.3
+ %.10 =w loadw %.9
+ %.11 =w sub %.10, 1
+ storew %.11, %.9
+ %.12 =l loadl %.7
+ %.13 =l vaarg %.3
+ %.14 =w loadw %.9
+ ret %.14
+}
+export
+function w $main() {
+@start.5
+ %.1 =l alloc4 4
+ %.5 =l alloc8 8
+ %.14 =l alloc8 8
+ %.19 =l alloc4 4
+ %.24 =l alloc4 4
+ %.45 =l alloc8 8
+ %.65 =l alloc8 8
+ %.79 =l alloc8 8
+@body.6
+ storew 0, %.1
+ %.2 =w call $f()
+ %.3 =l extsw %.2
+ %.4 =l mul %.3, 4
+ %.6 =l extsw 0
+ storel %.6, %.5
+ %.7 =w loadw %.1
+ %.8 =w loadw $c
+ %.9 =w cnew %.8, 1
+ %.10 =w add %.7, %.9
+ storew %.10, %.1
+ %.11 =w loadw $c
+ %.12 =w add %.11, 1
+ storew %.12, $c
+ %.13 =l loadl %.5
+ %.15 =w loadw %.1
+ %.16 =w loadw $c
+ %.17 =w cnew %.16, 2
+ %.18 =w add %.15, %.17
+ storew %.18, %.1
+ %.20 =w loadw %.1
+ %.21 =w loadw $c
+ %.22 =w cnew %.21, 2
+ %.23 =w add %.20, %.22
+ storew %.23, %.1
+ %.25 =w loadw %.1
+ %.26 =w loadw $c
+ %.27 =w cnew %.26, 2
+ %.28 =w add %.25, %.27
+ storew %.28, %.1
+ %.29 =w loadw %.1
+ %.30 =l loadl %.5
+ %.31 =l extsw 0
+ %.32 =w cnel %.30, %.31
+ %.33 =w add %.29, %.32
+ storew %.33, %.1
+ %.34 =w loadw $c
+ %.35 =w add %.34, 1
+ storew %.35, $c
+ storel $a, %.5
+ %.36 =l alloc4 %.4
+ %.37 =w loadw %.1
+ %.38 =w loadw $c
+ %.39 =w cnew %.38, 3
+ %.40 =w add %.37, %.39
+ storew %.40, %.1
+ %.41 =w loadw %.1
+ %.42 =l loadl %.5
+ %.43 =w cnel %.42, $a
+ %.44 =w add %.41, %.43
+ storew %.44, %.1
+ %.46 =w loadw %.1
+ %.47 =w loadw $c
+ %.48 =w cnew %.47, 3
+ %.49 =w add %.46, %.48
+ storew %.49, %.1
+ %.50 =w loadw %.1
+ %.51 =l loadl %.5
+ %.52 =w cnel %.51, $a
+ %.53 =w add %.50, %.52
+ storew %.53, %.1
+ %.54 =w loadw $c
+ %.55 =w add %.54, 1
+ storew %.55, $c
+ %.56 =l loadl %.5
+ %.57 =l extsw 0
+ %.58 =w loadw %.1
+ %.59 =w loadw $c
+ %.60 =w cnew %.59, 4
+ %.61 =w add %.58, %.60
+ storew %.61, %.1
+ %.62 =w loadw $c
+ %.63 =w add %.62, 1
+ storew %.63, $c
+ %.64 =l loadl %.5
+ %.66 =l extsw 0
+ storel %.66, %.65
+ %.67 =l loadl %.65
+ %.68 =w loadw %.1
+ %.69 =w loadw $c
+ %.70 =w cnew %.69, 5
+ %.71 =w add %.68, %.70
+ storew %.71, %.1
+ %.72 =w loadw %.1
+ %.73 =l loadl %.5
+ %.74 =w call $g(w 3, ..., l %.73)
+ %.75 =w add %.72, %.74
+ storew %.75, %.1
+ %.76 =w loadw $c
+ %.77 =w add %.76, 1
+ storew %.77, $c
+ %.78 =l loadl %.5
+ %.80 =w loadw %.1
+ %.81 =w loadw $c
+ %.82 =w cnew %.81, 6
+ %.83 =w add %.80, %.82
+ storew %.83, %.1
+ %.84 =w loadw %.1
+ ret %.84
+}
diff --git a/test/vla-deref.c b/test/vla-deref.c
new file mode 100644
index 0000000..7a5cd20
--- /dev/null
+++ b/test/vla-deref.c
@@ -0,0 +1,4 @@
+int main(void) {
+ int l = 3;
+ char a[*&l];
+}
diff --git a/test/vla-deref.qbe b/test/vla-deref.qbe
new file mode 100644
index 0000000..f9da148
--- /dev/null
+++ b/test/vla-deref.qbe
@@ -0,0 +1,12 @@
+export
+function w $main() {
+@start.1
+ %.1 =l alloc4 4
+@body.2
+ storew 3, %.1
+ %.2 =w loadw %.1
+ %.3 =l extsw %.2
+ %.4 =l mul %.3, 1
+ %.5 =l alloc4 %.4
+ ret 0
+}
diff --git a/test/vla-nested.c b/test/vla-nested.c
new file mode 100644
index 0000000..176392e
--- /dev/null
+++ b/test/vla-nested.c
@@ -0,0 +1,13 @@
+int l;
+int f(int x) {
+ l += x;
+ return x;
+}
+int main(void) {
+ int r = 0;
+ int (*p[f(2)])[f(3)];
+ r += l != 5;
+ r += sizeof p != sizeof(int (*[2])[3]);
+ r += sizeof **p != sizeof(int[3]);
+ return r;
+}
diff --git a/test/vla-nested.qbe b/test/vla-nested.qbe
new file mode 100644
index 0000000..b01c24c
--- /dev/null
+++ b/test/vla-nested.qbe
@@ -0,0 +1,44 @@
+export
+function w $f(w %.1) {
+@start.1
+ %.2 =l alloc4 4
+ storew %.1, %.2
+@body.2
+ %.3 =w loadw $l
+ %.4 =w loadw %.2
+ %.5 =w add %.3, %.4
+ storew %.5, $l
+ %.6 =w loadw %.2
+ ret %.6
+}
+export
+function w $main() {
+@start.3
+ %.1 =l alloc4 4
+@body.4
+ storew 0, %.1
+ %.2 =w call $f(w 3)
+ %.3 =l extsw %.2
+ %.4 =l mul %.3, 4
+ %.5 =w call $f(w 2)
+ %.6 =l extsw %.5
+ %.7 =l mul %.6, 8
+ %.8 =l alloc8 %.7
+ %.9 =w loadw %.1
+ %.10 =w loadw $l
+ %.11 =w cnew %.10, 5
+ %.12 =w add %.9, %.11
+ storew %.12, %.1
+ %.13 =w loadw %.1
+ %.14 =w cnel %.7, 16
+ %.15 =w add %.13, %.14
+ storew %.15, %.1
+ %.16 =w loadw %.1
+ %.17 =l loadl %.8
+ %.18 =w cnel %.4, 12
+ %.19 =w add %.16, %.18
+ storew %.19, %.1
+ %.20 =w loadw %.1
+ ret %.20
+}
+export data $l = align 4 { z 4 }
diff --git a/test/vla.c b/test/vla.c
new file mode 100644
index 0000000..61f3dcf
--- /dev/null
+++ b/test/vla.c
@@ -0,0 +1,5 @@
+short g() { return 1; }
+long f(void) {
+ double a[10 + g()];
+ return sizeof(a);
+}
diff --git a/test/vla.qbe b/test/vla.qbe
new file mode 100644
index 0000000..6c4f6bc
--- /dev/null
+++ b/test/vla.qbe
@@ -0,0 +1,18 @@
+export
+function w $g() {
+@start.1
+@body.2
+ ret 1
+}
+export
+function l $f() {
+@start.3
+@body.4
+ %.1 =w call $g()
+ %.2 =w extsh %.1
+ %.3 =w add 10, %.2
+ %.4 =l extsw %.3
+ %.5 =l mul %.4, 8
+ %.6 =l alloc8 %.5
+ ret %.5
+}
diff --git a/type.c b/type.c
index 671914f..0d548d8 100644
--- a/type.c
+++ b/type.c
@@ -67,6 +67,8 @@ mkpointertype(struct type *base, enum typequal qual)
t->qual = qual;
t->size = 8;
t->align = 8;
+ if (base)
+ t->prop |= base->prop & PROPVM;
return t;
}
@@ -112,6 +114,7 @@ bool
typecompatible(struct type *t1, struct type *t2)
{
struct decl *p1, *p2;
+ struct expr *e1, *e2;
if (t1 == t2)
return true;
@@ -128,7 +131,11 @@ typecompatible(struct type *t1, struct type *t2)
case TYPEPOINTER:
goto derived;
case TYPEARRAY:
- if (!t1->incomplete && !t2->incomplete && t1->size != t2->size)
+ if (t1->incomplete || t2->incomplete)
+ goto derived;
+ e1 = t1->u.array.length;
+ e2 = t2->u.array.length;
+ if (e1 && e2 && e1->kind == EXPRCONST && e2->kind == EXPRCONST && e1->u.constant.u != e2->u.constant.u)
return false;
goto derived;
case TYPEFUNC: