This is the mail archive of the gcc-prs@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

optimization/5477: gcc 3.0.x reserves a large stack frame, but uses only some parts of it



>Number:         5477
>Category:       optimization
>Synopsis:       gcc 3.0.x reserves a large stack frame, but uses only some parts of it
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    unassigned
>State:          open
>Class:          pessimizes-code
>Submitter-Id:   net
>Arrival-Date:   Thu Jan 24 01:06:00 PST 2002
>Closed-Date:
>Last-Modified:
>Originator:     Klaus Espenlaub
>Release:        gcc-3.0.3
>Organization:
>Environment:
System: Linux croc 2.4.7-4GB #1 Thu Oct 25 17:53:12 GMT 2001 i686 unknown
Architecture: i686
>Description:
All recent gcc C-compiler versions (starting from 3.0)
create code that has the distinctive tendency to allocate
stack frames that are much larger than required for the
respective function.  The situation is worsened by heavy
use of small functions declared __inline__.  In my case -
embedded environment with very tight stack - this makes
recent gcc versions unusable.

The attached sample compiles to a single function, which
(on the i386 architecture) reserves 380 bytes for local
variables.  The same code compiled with gcc 2.95.3 reserves
just 108 bytes.  If the inline function printf_putchar is
replaced by an equivalent preprocessor macro, then the
stack allocation is reduced drastically to 124 bytes.
Interestingly for gcc 2.95 it doesn't make a substantial
difference whether inline functions or preprocessor macros
are used, but for gcc 3.0 it does.

As a side note: the same code compiled by g++ creates code
that uses a reasonable amount of stack, regardless of the
compiler version (2.95 or 3.0.x).
>How-To-Repeat:
(assuming that the attached file - which BTW uses #include,
but only stuff that comes with gcc - is saved to
printf-stackwaste.c)

gcc -O2 -S printf-stackwaste.c
for the version using inlining, and
gcc -O2 -S -DPUTCHAR_MACRO printf-stackwaste.c
for the version using a preprocessor macro.

The interesting number is the value that is subtracted
from %esp at the end of the epilogue for do_printf.
>Fix:

>Release-Note:
>Audit-Trail:
>Unformatted:
----gnatsweb-attachment----
Content-Type: text/plain; name="printf-stackwaste.c"
Content-Disposition: inline; filename="printf-stackwaste.c"

#include <limits.h>
#include <stdarg.h>


/* Some gcc versions don't define LLONG_MAX, because it wasn't defined in the
 * earlier C standards.  */
#ifndef LLONG_MAX
#define LLONG_MAX 9223372036854775807LL
#endif /* LLONG_MAX */

#define NULL ((void *)0)


typedef unsigned int size_t;


#define ZEROPAD	1		/* pad with zero */
#define SIGN	2		/* unsigned/signed long */
#define PLUS	4		/* show plus */
#define SPACE	8		/* space if plus */
#define LEFT	16		/* left justified */
#define SPECIAL	32		/* 0x */
#define LARGE	64		/* use 'ABCDEF' instead of 'abcdef' */


#ifdef PUTCHAR_MACRO
#define printf_putchar(buf, c) \
	if (*(buf) != NULL) { \
		**(buf) = c; \
		(*(buf))++; \
	} else { \
		__console_putchar(c); \
	}
#else
static __inline__ void printf_putchar(char **buf, unsigned char c)
{
	if (*buf != NULL) {
		**buf = c;
		(*buf)++;
	} else {
		__console_putchar(c);
	}
}
#endif

static __inline__ void number(char **str, unsigned long long num,
                              unsigned base, int size, unsigned precision,
                              int type, size_t *maxlen)
{
	char sign, tmp[22];
	static const char digits[16] = "0123456789abcdef";
	unsigned i;

	if (type & LEFT)
		type &= ~ZEROPAD;
	if (base < 8 || base > 16)
		return;
	sign = 0;
	if (type & SIGN) {
		if (num > LLONG_MAX) {
			sign = '-';
			num = -num;
			size--;
		} else if (type & PLUS) {
			sign = '+';
			size--;
		} else if (type & SPACE) {
			sign = ' ';
			size--;
		}
	}
	if (type & SPECIAL) {
		if (base == 16)
			size -= 2;
		else if (base == 8)
			size--;
	}
	i = 0;
	if (num == 0)
		tmp[i++] = '0';
	else while (num != 0) {
		tmp[i] = digits[num % base];
		num /= base;
		if ((type & LARGE) && (tmp[i] > '9')) tmp[i] &= 0xdf;
		i++;
	}
	if ((i > precision) || (precision == UINT_MAX))
		precision = i;
	size -= precision;
	if (!(type&(ZEROPAD+LEFT)))
		while((size-->0) && (*maxlen > 0)) {
			printf_putchar(str, ' ');
			(*maxlen)--;
		}
	if (sign && (*maxlen > 0)) {
		printf_putchar(str, sign);
		(*maxlen)--;
	}
	if (type & SPECIAL) {
		if ((base==8) && (*maxlen > 0)) {
			printf_putchar(str, '0');
			(*maxlen)--;
		} else if (base==16) {
			if (*maxlen > 0) {
				printf_putchar(str, '0');
				(*maxlen)--;
			}
			if (*maxlen > 0) {
				printf_putchar(str, (type & LARGE) ? 'X' : 'x');
				(*maxlen)--;
			}
		}
	}
	if (!(type & LEFT))
		while ((size-- > 0) && (*maxlen > 0)) {
			printf_putchar(str, (type & ZEROPAD) ? '0' : ' ');
			(*maxlen)--;
		}
	while ((i < precision--) && (*maxlen > 0)) {
		printf_putchar(str, '0');
		(*maxlen)--;
	}
	while ((i-- > 0) && (*maxlen > 0)) {
		printf_putchar(str, tmp[i]);
		(*maxlen)--;
	}
	while ((size-- > 0) && (*maxlen > 0)) {
		printf_putchar(str, ' ');
		(*maxlen)--;
	}
}

static __inline__ unsigned skip_atoi(const char **s)
{
	unsigned i = 0;

	while ((**s >= '0') && (**s <= '9'))
		i = i*10 + *((*s)++) - '0';
	return i;
}

int do_printf(char *buf, size_t maxlen, const char *fmt, va_list args)
{
	int len;
	unsigned long long num;
	int i;
	unsigned base;
	char *str;
	const char *s;

	int flags;				/* flags to number() */
	int field_width;		/* width of output field */
	unsigned precision;		/* min. digits (int); max. chars (string) */
	int qualifier;			/* 'h', 'l', or 'L' for integer fields */

	for (str=buf ; *fmt ; ++fmt) {
		if (*fmt != '%') {
			if (maxlen > 0) {
				printf_putchar(&str, *fmt);
				maxlen--;
			}
			continue;
		}
			
		/* process flags */
		flags = 0;
		repeat:
			++fmt;		/* this also skips first '%' */
			switch (*fmt) {
				case '-': flags |= LEFT; goto repeat;
				case '+': flags |= PLUS; goto repeat;
				case ' ': flags |= SPACE; goto repeat;
				case '#': flags |= SPECIAL; goto repeat;
				case '0': flags |= ZEROPAD; goto repeat;
				}
		
		/* get field width */
		field_width = -1;
		if ((*fmt >= '0') && (*fmt <= '9'))
			field_width = skip_atoi(&fmt);
		else if (*fmt == '*') {
			++fmt;
			/* it's the next argument */
			field_width = va_arg(args, int);
			if (field_width < 0) {
				field_width = -field_width;
				flags |= LEFT;
			}
		}

		/* get the precision */
		precision = UINT_MAX;
		if (*fmt == '.') {
			++fmt;	
			if ((*fmt >= '0') && (*fmt <= '9'))
				precision = skip_atoi(&fmt);
			else if (*fmt == '*') {
				++fmt;
				/* it's the next argument */
				precision = va_arg(args, unsigned int);
				if ((signed) precision < 0)
					precision = 0;
			}
		}

		/* get the conversion qualifier */
		qualifier = -1;
		if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {
			qualifier = *fmt;
			++fmt;
		}

		/* default base */
		base = 10;

		switch (*fmt) {
		case 'c':
			if (!(flags & LEFT))
				while ((--field_width > 0) && (maxlen > 0)) {
					printf_putchar(&str, ' ');
					maxlen--;
				}
			if (maxlen > 0) {
				printf_putchar(&str, (unsigned char) va_arg(args, int));
				maxlen--;
			}
			while ((--field_width > 0) && (maxlen > 0)) {
				printf_putchar(&str, ' ');
				maxlen--;
			}
			continue;

		case 's':
			s = va_arg(args, char *);
			if (!s)
				s = "<NULL>";

			len = strnlen(s, precision);

			if (!(flags & LEFT))
				while ((len < field_width--) && (maxlen > 0)) {
					printf_putchar(&str, ' ');
					maxlen--;
				}
			for (i = 0; (i < len) && (maxlen > 0); ++i) {
				printf_putchar(&str, *s++);
				maxlen--;
			}
			while ((len < field_width--) && (maxlen > 0)) {
				printf_putchar(&str, ' ');
				maxlen--;
			}
			continue;

		case 'n':
			if (qualifier == 'l') {
				long *ip = va_arg(args, long *);
				*ip = (str - buf);
			} else if (qualifier == 'L') {
				long long *ip = va_arg(args, long long *);
				*ip = (str - buf);
			} else {
				int *ip = va_arg(args, int *);
				*ip = (str - buf);
			}
			continue;

		/* integer number formats - set up the flags and "break" */
		case 'o':
			base = 8;
			break;

		case 'X':
			flags |= LARGE;
		case 'x':
			base = 16;
			break;

		case 'd':
		case 'i':
			flags |= SIGN;
		case 'u':
			break;

		case 'p':
			if (field_width == -1) {
				field_width = 2*sizeof(void *);
				flags |= ZEROPAD;
				flags &= ~SIGN;
			}
			qualifier = '@';
			base = 16;
			break;

		default:
			if ((*fmt != '%') && (maxlen > 0)) {
				printf_putchar(&str, '%');
				maxlen--;
			}
			if (*fmt) {
				if (maxlen > 0) {
					printf_putchar(&str, *fmt);
					maxlen--;
				}
			} else {
				--fmt;
			}
			continue;
		}
		if (qualifier == 'l') {
			if (flags & SIGN) {
				num = va_arg(args, long);
			} else {
				num = va_arg(args, unsigned long);
			}
		} else if (qualifier == 'L') {
			if (flags & SIGN) {
				num = va_arg(args, long long);
			} else {
				num = va_arg(args, unsigned long long);
			}
		} else if (qualifier == 'h') {
			if (flags & SIGN) {
				num = va_arg(args, int); /* promoted!!! */
			} else {
				num = va_arg(args, int); /* promoted!!! */
			}
		} else if (qualifier == '@') {
			/* Fake qualifier to fit %p formats into integer formats.  */
			num = (unsigned long)va_arg(args, void *);
		} else if (flags & SIGN) {
			num = va_arg(args, int);
		} else {
			num = va_arg(args, unsigned int);
		}
		number(&str, num, base, field_width, precision, flags, &maxlen);
	}
	if (maxlen > 0) {
		if (str != NULL) {
			*str = '\0';
		}
		maxlen--;
		return maxlen;
	} else {
		return -1;
	}
}


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]