From 4f206ac1ea1b20400fa242f2f3be86237c4ba3bf Mon Sep 17 00:00:00 2001 From: Nihal Jere Date: Sat, 27 Apr 2024 14:47:15 -0700 Subject: 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 --- cc.h | 11 ++- configure | 1 - decl.c | 50 +++++++---- expr.c | 32 ++++--- init.c | 8 +- qbe.c | 198 +++++++++++++++++++++++++----------------- test/alignas-vla-strict.c | 5 ++ test/alignas-vla-strict.qbe | 16 ++++ test/builtin-vaarg-vm.c | 16 ++++ test/builtin-vaarg-vm.qbe | 37 ++++++++ test/cast-vm.c | 6 ++ test/cast-vm.qbe | 22 +++++ test/compatible-vla-types.c | 17 ++++ test/compatible-vla-types.qbe | 11 +++ test/func-vla.c | 8 ++ test/func-vla.qbe | 24 +++++ test/sizeof-vla.c | 19 ++++ test/sizeof-vla.qbe | 97 +++++++++++++++++++++ test/typeof-vm.c | 49 +++++++++++ test/typeof-vm.qbe | 148 +++++++++++++++++++++++++++++++ test/vla-deref.c | 4 + test/vla-deref.qbe | 12 +++ test/vla-nested.c | 13 +++ test/vla-nested.qbe | 44 ++++++++++ test/vla.c | 5 ++ test/vla.qbe | 18 ++++ type.c | 9 +- 27 files changed, 770 insertions(+), 110 deletions(-) create mode 100644 test/alignas-vla-strict.c create mode 100644 test/alignas-vla-strict.qbe create mode 100644 test/builtin-vaarg-vm.c create mode 100644 test/builtin-vaarg-vm.qbe create mode 100644 test/cast-vm.c create mode 100644 test/cast-vm.qbe create mode 100644 test/compatible-vla-types.c create mode 100644 test/compatible-vla-types.qbe create mode 100644 test/func-vla.c create mode 100644 test/func-vla.qbe create mode 100644 test/sizeof-vla.c create mode 100644 test/sizeof-vla.qbe create mode 100644 test/typeof-vm.c create mode 100644 test/typeof-vm.qbe create mode 100644 test/vla-deref.c create mode 100644 test/vla-deref.qbe create mode 100644 test/vla-nested.c create mode 100644 test/vla-nested.qbe create mode 100644 test/vla.c create mode 100644 test/vla.qbe 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: -- cgit v1.2.3