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