[gnu.gcc.bug] Incorrect code generated when compiling with -O -fstrength-reduce

jk@UUNET.UU.NET (Juergen Keil) (12/17/89)

I am using a SUN3/50, SunOS 3.5 and gcc 1.36 configured for sun3-os3.
gcc 1.36 produces incorrect assembly code when compiling the following
program with -O and -fstrength-reduce (reduced from a much larger program 
to one function): 

-- error1.c ------------
unsigned short x;
unsigned short y;
int arr[100];

main()
{
	int n;
	int s;

	arr[x] = 1;
	n = x;
	s = y;
	while (n < y) {
		s += arr[n];
		n++;
	}
	f(s);
}
------------------------

$ gcc -v -S -O -fstrength-reduce error1.c
 /usr/local/lib/gcc-cpp -v -undef -D__GNUC__ -Dmc68000 -Dsun -Dunix -D__mc68000__ -D__sun__ -D__unix__ -D__OPTIMIZE__ -D__HAVE_68881__ -Dmc68020 error1.c /tmp/cca11466.cpp
GNU CPP version 1.36
 /usr/local/lib/gcc-cc1 /tmp/cca11466.cpp -quiet -dumpbase error1.c -fstrength-reduce -O -version -o error1.s
GNU C version 1.36 (68k, MIT syntax) compiled by GNU C version 1.36.
default target switches: -m68020 -mc68020 -m68881 -mbitfield

The following assembly code is generated:

#NO_APP
gcc_compiled.:
.text
        .even
.globl _main
_main:
        link a6,#0
        moveml #0x3800,sp@-
        clrl d0
        movew _x,d0
        lea _arr,a0
        moveq #1,d4
        movel d4,a0@(d0:l:4)
        clrl d1
        movew _x,d1
        clrl d2
        movew _y,d2
        clrl d3
        lea _arr,a0	; <-- $a0 = &arr[0] instead of $a0 = &arr[n] !!
        jra L2
L4:
        addl a0@+,d2
        movel a0,d0
        addql #1,d1
L2:
        movew _y,d3
        cmpl d1,d3
        jgt L4
        movel d2,sp@-
        jbsr _f
        moveml a6@(-12),#0x1c
        unlk a6
        rts
.comm _arr,400
.comm _y,2
.comm _x,2

When gcc searches the last assignment to a biv before a loop it misses
assignments where only parts of the register containg the biv are
modified. In this case the initialisation of n is done by first setting n 
to 0 and then loading the lower half of n with (the unsigned short) x. gcc
misses the insn where the lower half of n is loaded and instead finds the
insn where n is set to 0!

I used the following patch to fix the problem:

*** loop.c	Sat Dec 16 18:27:47 1989
--- loop.c.orig	Sat Dec 16 18:23:22 1989
***************
*** 2553,2572 ****
  	call_seen = 1;
  
        if (GET_CODE (p) == INSN
! 	  && GET_CODE (PATTERN (p)) == SET)
  	{
! 	  rtx dest = SET_DEST (PATTERN (p));
  
- 	  while (GET_CODE (dest) == SUBREG
- 		 || GET_CODE (dest) == ZERO_EXTRACT
- 		 || GET_CODE (dest) == SIGN_EXTRACT
- 		 || GET_CODE (dest) == STRICT_LOW_PART)
- 	    dest = XEXP (dest, 0);
- 
- 	  if (GET_CODE (dest) == REG)
- 	    {
- 	      int dest_regno = REGNO (dest);
- 
  	      if (induct_var[dest_regno] == BASIC_INDUCT
  		  && class_struct[dest_regno]->init_insn == 0)
  		{
--- 2553,2563 ----
  	call_seen = 1;
  
        if (GET_CODE (p) == INSN
! 	  && GET_CODE (PATTERN (p)) == SET
! 	  && GET_CODE (SET_DEST (PATTERN (p))) == REG)
  	{
! 	  int dest_regno = REGNO (SET_DEST (PATTERN (p)));
  
  	  if (induct_var[dest_regno] == BASIC_INDUCT
  	      && class_struct[dest_regno]->init_insn == 0)
  	    {
***************
*** 2617,2623 ****
  		    }
  
  		  biv_found--;
- 		}
  	    }
          }
        else if (GET_CODE (p) == CODE_LABEL)
--- 2608,2613 ----


Having fixed this bug I tried my hacked gcc on the original program but
it still didn't get the assembly code right. The second bug can be 
reproduced by the following program:

-- error2.c ----------
unsigned short x;
unsigned short y;
int arr[100];

main()
{
	int n;
	int m;
	int s;

	for (m = 1; m < 10; m++) {
		arr[m] = m;
		arr[x] = 1;
		n = x;
		s = y;
		while (n < y) {
			s += arr[n];
			n++;
		}
		f(s);
	}
}
-----------------------

$ gcc -v -S -O -fstrength-reduce error2.c
gcc version 1.36
 /usr/local/lib/gcc-cpp -v -undef -D__GNUC__ -Dmc68000 -Dsun -Dunix -D__mc68000__ -D__sun__ -D__unix__ -D__OPTIMIZE__ -D__HAVE_68881__ -Dmc68020 error2.c /tmp/cca11525.cpp
GNU CPP version 1.36
 /usr/local/lib/gcc-cc1 /tmp/cca11525.cpp -quiet -dumpbase error2.c -fstrength-reduce -O -version -o error2.s
GNU C version 1.36 (68k, MIT syntax) compiled by GNU C version 1.36.
default target switches: -m68020 -mc68020 -m68881 -mbitfield

This time, the following assembly code is generated:

#NO_APP
gcc_compiled.:
.text
	.even
.globl _main
_main:
	link a6,#0
	moveml #0x3e20,sp@-
	moveq #1,d5
	lea _arr,a2
	clrl d2
L8:
	movel d5,a2@(d5:l:4)
	movew _x,d2		; x loaded into $d2
	moveq #1,d6
	movel d6,a2@(d2:l:4)
	movel d2,d3		; n = x			(@)
	movew _y,d2		; Now $d2 contains y
	movel d2,d4
	clrl d1
	lea a2@(d2:l:4),a0	; $a0 = &arr[y] instead of &arr[x]
				; should use $d3 instead of $d2 here!
	jra L5
L7:
	addl a0@+,d4
	movel a0,d0
	addql #1,d3
L5:
	movew _y,d1
	cmpl d3,d1
	jgt L7
	movel d4,sp@-
	jbsr _f
	addqw #4,sp
	addql #1,d5
	moveq #9,d6
	cmpl d5,d6
	jge L8
	moveml a6@(-24),#0x47c
	unlk a6
	rts
.comm _arr,400
.comm _y,2
.comm _x,2

The problem in this case is, that the strength reduction of the inner loop
possibly extends the lifetime of a register that is used to initialize
the biv ($d2) up to the beginning of the loop, but the information in
the array regno_last_uid is not updated. The strength reduction of
the outer loop considers $d2 as unused after (@) und re-uses $d2 for
optimizing another load of an unsigned short variable.

This can be fixed by applying the following patch to loop.c (a better patch
would probably update the lifetime information for the register involved
but I haven't figured out yet how to do this):

*** loop2.c	Sat Dec 16 19:30:54 1989
--- loop.c	Sat Dec 16 18:27:47 1989
***************
*** 2584,2590 ****
  
  		  /* Save value if it is a constant or register.  */
  		  if (CONSTANT_P (src)
- #if 0
  		      || (GET_CODE (src) == REG
  			  /* Don't try to use a value in a hard reg
  			     across a call which clobbers it.  */
--- 2584,2589 ----
***************
*** 2591,2599 ****
  			  && ! (REGNO (src) < FIRST_PSEUDO_REGISTER
  				&& call_used_regs[REGNO (src)]
  				&& call_seen)
! 			  && ! reg_set_between_p (src, p, loop_start))
! #endif
! 								      )
  		    {
  		      class_struct[dest_regno]->initial_value = src;
  
--- 2590,2596 ----
  			  && ! (REGNO (src) < FIRST_PSEUDO_REGISTER
  				&& call_used_regs[REGNO (src)]
  				&& call_seen)
! 			  && ! reg_set_between_p (src, p, loop_start)))
  		    {
  		      class_struct[dest_regno]->initial_value = src;
 
--
Juergen Keil                 jk@tools.uucp  ...!{uunet,mcsun}!unido!tools!jk