[gnu.gcc.bug] GCC 1.35 '-fcaller-saves' option generates incompatible use of %a6

lgm@ODDJOB.UCHICAGO.EDU (05/01/89)

SYNOPSIS:
The '-fcaller-saves' option can cause GCC 1.35 to generate incorrect
code.  Specifically:  When used with the other optimizing options

	-O
	-fomit-frame-pointer
	-fstrength-reduce
	-fcombine-regs
	-fforce-mem
	-fforce-addr

the additional '-fcaller-saves' option can cause GCC 1.35 to attempt
to treat  %fp  (on the Motorola 680x0, otherwise known as  %a6 )
as *both* a frame pointer and a scratch register.  The resulting
machine code core-dumps.  Oddly enough, in the example I list here
the *same* C function translates into either good or bad code,
depending on whether a certain other function appears above it in
the file.

MACHINE AND OS:
AT&T UNIX PC 3B1 (based on Motorola 68010 microprocessor) running
OS Version 3.5 (compatible with UNIX System V Release 2).

CONFIGURATION:
'config.gcc 3b1'

TRANSCRIPT OF COMMAND LINE:
________________
$ echo $GCCOPTS
-O -fomit-frame-pointer -fcombine-regs -fforce-mem -fforce-addr -fstrength-reduce
$ gcc -v -S $GCCOPTS -fcaller-saves bad.c
gcc version 1.35
 /usr/local/lib/gcc-cpp -v -undef -D__GNUC__ -Dmc68k -Dunix -Dunixpc -D__mc68k__ -D__unix__ -D__unixpc__ -D__OPTIMIZE__ bad.c /tmp/cca17150.cpp
GNU CPP version 1.35
 /usr/local/lib/gcc-cc1 /tmp/cca17150.cpp -quiet -dumpbase bad.c -fomit-frame-pointer -fcombine-regs -fforce-mem -fforce-addr -fstrength-reduce -fcaller-saves -O -version -o bad.s
GNU C version 1.35 (68k, SGS/AT&T unixpc syntax) compiled by GNU C version 1.35.
$
_______________________________________

INPUT FILE THAT GENERATES BAD CODE:
_________________
typedef unsigned char U_CHAR;

char *xmalloc (), *xrealloc (), *xcalloc (), *savestring ();

extern int cplusplus;
extern int put_out_comments;
extern int traditional;

struct file_buf {
  char *fname;
  int lineno;
  int length;
  U_CHAR *buf;
  U_CHAR *bufp;
  struct hashnode *macro;
  struct if_stack *if_stack;
  U_CHAR *free_ptr;
};

typedef struct file_buf FILE_BUF;

extern FILE_BUF outbuf;

typedef struct definition DEFINITION;

struct definition {
  int nargs;
  int length;			 
  U_CHAR *expansion;
  struct reflist {
    struct reflist *next;
    char stringify;		 
    char raw_before;		 
    char raw_after;		 
    int nchars;			 
    int argno;			 
  } *pattern;
  U_CHAR *argnames;
};

union hashval {
  int ival;
  char *cpval;
  DEFINITION *defn;
};
 
enum node_type {
 T_DEFINE = 1,	 
 T_INCLUDE,	 
 T_IFDEF,	 
 T_IFNDEF,	 
 T_IF,		 
 T_ELSE,	 
 T_PRAGMA,	 
 T_ELIF,	 
 T_UNDEF,	 
 T_LINE,	 
 T_ERROR,	 
 T_ENDIF,	 
 T_SCCS,	 
 T_IDENT,	 
 T_SPECLINE,	 
 T_DATE,	 
 T_FILE,	 
 T_BASE_FILE,	 
 T_VERSION,	 
 T_TIME,	 
 T_CONST,	 
 T_MACRO,	 
 T_DISABLED,	 
 T_SPEC_DEFINED,  
 T_UNUSED	 
 };

struct hashnode {
  struct hashnode *next;	 
  struct hashnode *prev;
  struct hashnode **bucket_hdr;	 
  enum node_type type;		 
  int length;			 
  U_CHAR *name;			 
  union hashval value;		 
};

extern U_CHAR is_idchar[];
 
extern U_CHAR is_idstart[];
 
extern U_CHAR is_hor_space[];
 
extern U_CHAR is_space[];
  
struct if_stack {
  struct if_stack *next;	 
  char *fname;		 
  int lineno;			 
  int if_succeeded;		 

  enum node_type type;		 
};

int
newline_fix ();

struct arglist {
  struct arglist *next;
  U_CHAR *name;
  int length;
  int argno;
};

DEFINITION *
collect_expansion (buf, end, nargs, arglist)
     U_CHAR *buf, *end;
     int nargs;
     struct arglist *arglist;
{
  DEFINITION *defn;
  register U_CHAR *p, *limit, *lastp, *exp_p;
  struct reflist *endpat = 	0 ;
  U_CHAR *concat = 0;
  U_CHAR *stringify = 0;
  int maxsize;
  int expected_delimiter = '\0';

  if (end < buf)
    abort ();

  limit = end;
  p = buf;
  while (p < limit && is_space[limit[-1]]) limit--;
  while (p < limit && is_space[*p]) p++;

  maxsize = (sizeof (DEFINITION)
	     + 2 * (end - limit) + 2 * (p - buf)
	     + (limit - p) + 3);
  defn = (DEFINITION *) xcalloc (1, maxsize);

  defn->nargs = nargs;
  exp_p = defn->expansion = (U_CHAR *) defn + sizeof (DEFINITION);
  lastp = exp_p;

  p = buf;

  while (p < limit && is_space[*p]) {
    *exp_p++ = '\n';
    *exp_p++ = *p++;
  }

  while (p < limit) {
    int skipped_arg = 0;
    register U_CHAR c = *p++;

    *exp_p++ = c;

    if (!traditional) {
      switch (c) {
      case '\'':
      case '\"':
	for (; p < limit && *p != c; p++) {
	  *exp_p++ = *p;
	  if (*p == '\\') {
	    *exp_p++ = *++p;
	  }
	}
	*exp_p++ = *p++;
	break;

      case '\\':
	if (p < limit && *p == '#') {

	  exp_p--;
	  *exp_p++ = *p++;
	} else if (p < limit) {

	  *exp_p++ = *p++;
	}
	break;

      case '#':
	if (p < limit && *p == '#') {

	  exp_p--;
	  while (exp_p > lastp && is_hor_space[exp_p[-1]])
	    --exp_p;

	  p++;

	  do { while (is_hor_space[*p]) p++; } while (0) ;
	  concat = p;
	} else {

	  exp_p--;
	  do { while (is_hor_space[*p]) p++; } while (0) ;
	  if (p == limit || ! is_idstart[*p] || nargs <= 0)
	    error ("# operator should be followed by a macro argument name\n");
	  else
	    stringify = p;
	}
	break;
      }
    } else {

      switch (c) {
      case '\'':
      case '\"':
	if (expected_delimiter != '\0') {
	  if (c == expected_delimiter)
	    expected_delimiter = '\0';
	} else
	  expected_delimiter = c;
	break;

      case '\\':

	if (expected_delimiter != 0 && p < limit
	    && (*p == expected_delimiter || *p == '\\')) {
	  *exp_p++ = *p++;
	  continue;
	}
	break;

      case '/':
	if (expected_delimiter != '\0')  
	  break;
	if (*p == '*') {

	  exp_p--;
	  p += 1;
	  while (p < limit && !(p[-2] == '*' && p[-1] == '/'))
	    p++;

	  concat = p;
	}
	break;
      }
    }

    if (is_idchar[c] && nargs > 0) {
      U_CHAR *id_beg = p - 1;
      int id_len;

      --exp_p;
      while (p != limit && is_idchar[*p]) p++;
      id_len = p - id_beg;

      if (is_idstart[c]) {
	register struct arglist *arg;

	for (arg = arglist; arg != 	0 ; arg = arg->next) {
	  struct reflist *tpat;

	  if (arg->name[0] == c
	      && arg->length == id_len
	      && strncmp (arg->name, id_beg, id_len) == 0) {

	    tpat = (struct reflist *) xmalloc (sizeof (struct reflist));
	    tpat->next = 	0 ;
	    tpat->raw_before = concat == id_beg;
	    tpat->raw_after = 0;
	    tpat->stringify = (traditional ? expected_delimiter != '\0'
			       : stringify == id_beg);

	    if (endpat == 	0 )
	      defn->pattern = tpat;
	    else
	      endpat->next = tpat;
	    endpat = tpat;

	    tpat->argno = arg->argno;
	    tpat->nchars = exp_p - lastp;
	    {
	      register U_CHAR *p1 = p;
	      do { while (is_hor_space[*p1]) p1++; } while (0) ;
	      if (p1 + 2 <= limit && p1[0] == '#' && p1[1] == '#')
		tpat->raw_after = 1;
	    }
	    lastp = exp_p;	 
	    skipped_arg = 1;
	    break;
	  }
	}
      }

      if (! skipped_arg) {
	register U_CHAR *lim1 = p;
	p = id_beg;
	while (p != lim1)
	  *exp_p++ = *p++;
	if (stringify == id_beg)
	  error ("# operator should be followed by a macro argument name\n");
      }
    }
  }

  if (limit < end) {

    while (limit < end && is_space[*limit]) {
      *exp_p++ = '\n';
      *exp_p++ = *limit++;
    }
  } else if (!traditional) {

    *exp_p++ = '\n';
    *exp_p++ = ' ';
  }

  *exp_p = '\0';

  defn->length = exp_p - defn->expansion;

  if (defn->length + 1 > maxsize)
    abort ();

  return defn;
}

U_CHAR *
skip_to_end_of_comment (ip, line_counter)
     register FILE_BUF *ip;
     int *line_counter;		 
{
  register U_CHAR *limit = ip->buf + ip->length;
  register U_CHAR *bp = ip->bufp;
  FILE_BUF *op = &outbuf;	 
  int output = put_out_comments && !line_counter;

  if (output) {
    *op->bufp++ = '/';
    *op->bufp++ = '*';
  }
  if (cplusplus && bp[-1] == '/') {
    if (output) {
      while (bp < limit)
	if ((*op->bufp++ = *bp++) == '\n') {
	  bp--;
	  break;
	}
      op->bufp[-1] = '*';
      *op->bufp++ = '/';
      *op->bufp++ = '\n';
    } else {
      while (bp < limit) {
	if (*bp++ == '\n') {
	  bp--;
	  break;
	}
      }
    }
    ip->bufp = bp;
    return bp;
  }
  while (bp < limit) {
    if (output)
      *op->bufp++ = *bp;
    switch (*bp++) {
    case '\n':
      if (line_counter != 	0 )
	++*line_counter;
      if (output)
	++op->lineno;
      break;
    case '*':
      if (*bp == '\\' && bp[1] == '\n')
	newline_fix (bp);
      if (*bp == '/') {
        if (output)
	  *op->bufp++ = '/';
	ip->bufp = ++bp;
	return bp;
      }
      break;
    }
  }
  ip->bufp = bp;
  return bp;
}
_________________________________

ASSEMBLY LANGUAGE OUTPUT OF COMPILATION:
______________
	file	"bad.c"
text
LC%0:
	byte	'#,0x20,'o,'p,'e,'r,'a,'t,'o,'r,0x20,'s,'h,'o,'u,'l,'d,0x20,'b
	byte 'e,0x20,'f,'o,'l,'l,'o,'w,'e,'d,0x20,'b,'y,0x20,'a,0x20,'m,'a
	byte 'c,'r,'o,0x20,'a,'r,'g,'u,'m,'e,'n,'t,0x20,'n,'a,'m,'e,0xa,0x0
	even
	global collect_expansion
collect_expansion:
	link.w %a6,&-28
	movm.l &0x3f3c,-(%sp)
# ASSEMBLY LANGUAGE FOR  collect_expansion  ABBREVIATED
	movm.l -68(%a6),&0x3cfc
	unlk %a6
	rts
	even
	global skip_to_end_of_comment
skip_to_end_of_comment:
	link.w %a6,&0			# %a6  set up as frame pointer
	movm.l &0x383c,-(%sp)
	mov.l 8(%fp),%a5
	mov.l 12(%fp),%a4
	mov.l 12(%a5),%d4
	add.l 8(%a5),%d4
	mov.l 16(%a5),%a2
	lea outbuf,%fp			# %fp  ( %a6 )  overwritten!
	clr.l %d0
	tst.l put_out_comments
	beq.w L%120
	cmp.w %a4,&0
	bne.w L%120
	mov.l &1,%d0
L%120:
	mov.l %d0,%d2
	beq.w L%121
	lea 16(%fp),%a0
	mov.l (%a0),%a1
	mov.b &47,(%a1)
	addq.l &1,(%a0)
	mov.l (%a0),%a1
	mov.b &42,(%a1)
	addq.l &1,(%a0)
L%121:
	tst.l cplusplus
	beq.w L%122
	cmp.b -1(%a2),&47
	bne.w L%122
	tst.l %d2
	beq.w L%129
	cmp.l %d4,%a2
	bls.w L%125
	lea 16(%fp),%a1
L%127:
	mov.l (%a1),%a0
	mov.b (%a2)+,%d0
	mov.b %d0,(%a0)
	addq.l &1,(%a1)
	cmp.b %d0,&10
	bne.w L%124
	sub.w &1,%a2
	bra.w L%125
L%124:
	cmp.l %d4,%a2
	bhi.w L%127
L%125:
	lea 16(%fp),%a0
	mov.l (%a0),%a1
	sub.w &1,%a1
	mov.b &42,(%a1)
	mov.l (%a0),%a1
	mov.b &47,(%a1)
	addq.l &1,(%a0)
	mov.l (%a0),%a1
	mov.b &10,(%a1)
	addq.l &1,(%a0)
	bra.w L%150
L%132:
	cmp.b (%a2)+,&10
	bne.w L%129
	sub.w &1,%a2
	bra.w L%150
L%129:
	cmp.l %d4,%a2
	bhi.w L%132
	bra.w L%150
L%122:
	cmp.l %d4,%a2
	bls.w L%147
	lea 16(%fp),%a3
	and.l &255,%d3
L%146:
	tst.l %d2
	beq.w L%135
	mov.l (%a3),%a0
	mov.b (%a2),(%a0)
	addq.l &1,(%a3)
L%135:
	mov.b (%a2)+,%d3
	mov.l &10,%d1
	cmp.l %d1,%d3
	beq.w L%137
	mov.l &42,%d1
	cmp.l %d1,%d3
	beq.w L%140
	bra.w L%133
L%137:
	cmp.w %a4,&0
	beq.w L%138
	addq.l &1,(%a4)
L%138:
	tst.l %d2
	beq.w L%133
	addq.l &1,4(%fp)
	bra.w L%133
L%140:
	cmp.b (%a2),&92
	bne.w L%141
	cmp.b 1(%a2),&10
	bne.w L%141
	mov.l %a2,-(%sp)
	jsr newline_fix
	addq.w &4,%sp
L%141:
	cmp.b (%a2),&47
	bne.w L%133
	tst.l %d2
	beq.w L%143
	mov.l (%a3),%a0
	mov.b &47,(%a0)
	addq.l &1,(%a3)
L%143:
	addq.w &1,%a2
	bra.w L%150
L%133:
	cmp.l %d4,%a2
	bhi.w L%146
L%147:
L%150:
	mov.l %a2,16(%a5)
	mov.l %a2,%d0
	movm.l -28(%a6),&0x3c1c
	unlk %a6			# Overwritten  %a6  fed into %sp
	rts				# Jump to garbage core-dumps
_________________________________

COMMENTS:
At the beginning and end of function  skip_to_end_of_comment ,
register  %a6  plays its familiar role of a frame pointer.
But in the "interior" of the function,  %fp  (which is  %a6 , of course)
is written and read as a pointer to  outbuf .  When the function
completes,  unlk %a6  stores the address of  outbuf  into the stack
pointer  %sp ; then  rts  attempts to return to the address pointed
to by  %sp , and a core dump results.

Interestingly, if one simply deletes function  collect_expansion ,
the generated code for function  skip_to_end_of_comment  does *not*
attempt to use  %a6  as a frame pointer - i.e., there is no conflict
and hence no core dump.


	Lawrence G. Mayka
	Aurora, Illinois

	lgm@lmayk.UUCP
	nucsrl!chinet!lmayk!lgm