diff options
| author | Nihal Jere <nihal@nihaljere.xyz> | 2024-04-27 14:47:15 -0700 | 
|---|---|---|
| committer | Michael Forney <mforney@mforney.org> | 2024-04-27 15:34:03 -0700 | 
| commit | 4f206ac1ea1b20400fa242f2f3be86237c4ba3bf (patch) | |
| tree | 2dc2742c051e6601874fc699265da40fd51bd5e5 | |
| parent | 487079af3d6f39b379f52f7e0ea6edec63587e5b (diff) | |
| download | cproc-4f206ac1ea1b20400fa242f2f3be86237c4ba3bf.tar.xz | |
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.h | 11 | ||||
| -rwxr-xr-x | configure | 1 | ||||
| -rw-r--r-- | decl.c | 50 | ||||
| -rw-r--r-- | expr.c | 32 | ||||
| -rw-r--r-- | init.c | 8 | ||||
| -rw-r--r-- | qbe.c | 198 | ||||
| -rw-r--r-- | test/alignas-vla-strict.c | 5 | ||||
| -rw-r--r-- | test/alignas-vla-strict.qbe | 16 | ||||
| -rw-r--r-- | test/builtin-vaarg-vm.c | 16 | ||||
| -rw-r--r-- | test/builtin-vaarg-vm.qbe | 37 | ||||
| -rw-r--r-- | test/cast-vm.c | 6 | ||||
| -rw-r--r-- | test/cast-vm.qbe | 22 | ||||
| -rw-r--r-- | test/compatible-vla-types.c | 17 | ||||
| -rw-r--r-- | test/compatible-vla-types.qbe | 11 | ||||
| -rw-r--r-- | test/func-vla.c | 8 | ||||
| -rw-r--r-- | test/func-vla.qbe | 24 | ||||
| -rw-r--r-- | test/sizeof-vla.c | 19 | ||||
| -rw-r--r-- | test/sizeof-vla.qbe | 97 | ||||
| -rw-r--r-- | test/typeof-vm.c | 49 | ||||
| -rw-r--r-- | test/typeof-vm.qbe | 148 | ||||
| -rw-r--r-- | test/vla-deref.c | 4 | ||||
| -rw-r--r-- | test/vla-deref.qbe | 12 | ||||
| -rw-r--r-- | test/vla-nested.c | 13 | ||||
| -rw-r--r-- | test/vla-nested.qbe | 44 | ||||
| -rw-r--r-- | test/vla.c | 5 | ||||
| -rw-r--r-- | test/vla.qbe | 18 | ||||
| -rw-r--r-- | type.c | 9 | 
27 files changed, 770 insertions, 110 deletions
| @@ -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 *); @@ -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 */ @@ -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)) { @@ -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; @@ -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) @@ -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 +} @@ -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: | 
