This is the mail archive of the gcc-bugs@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]

Gcc 2.91.6x, 2.95.2, 2.95.3 for armlinux: wrong switch alternative taken after r/o comparison with selector variable


Hello,
I would like to report a possible bug in various gcc versions for arm-linux.

I tried to reduce the code leading to the error as much as possible
and eventually arrived at the following bogus program:

int global_var;

int switch_test(void) {
    int              sw = 4;
    int dmy      = (sw &77) ? 11 : 0x101;
    global_var = dmy * ((sw >= 5) +1);

    switch (sw) {
        case 4:  return(0);    // okay
        default: return(99);  //error
    }
 }

int main(void) {
  printf("result=%d\n", switch_test()); return(0);
}                            
 
When executed, the function returns "99", i.e. at the time the switch(sw) is
calculated, the wrong case is taken. 

The following assembly code is generated :

 * 13                                                      .global switch_test
 * 14                                                      .type      switch_test,function
 * 15                                      switch_test:
 * 16                                                      @ args = 0, pretend = 0, frame = 0
 * 17                                                      @ frame_needed = 1, current_function_anonymous_args = 0
 * 18 0000 0DC0A0E1                          mov        ip, sp
 * 19 0004 00D82DE9                          stmfd    sp!, {fp, ip, lr, pc}
 * 20 0008 04B04CE2                          sub        fp, ip, #4
 * 21 000c 0B30A0E3                          mov        r3, #11          ; dmy = constant 11
 * 22 0010 08209FE5                          ldr        r2, .L40        ; load adrs of global
 * 23 0014 6300A0E3                          mov        r0, #99          ; load result = 99 (wrong !!!)
 * 24 0018 003082E5                          str        r3, [r2, #0] ; store dmy = 257 into global
 * 25 001c 00A81BE9                          ldmea    fp, {fp, sp, pc}
 * 27                                      .L41:
 * 28                                                      .align  2
 * 29                                      .L40:
 * 30 0024 00000000                          .word    global

Obviously, gcc optimizes away all the constant expressions and produces optimum
code, but then decides to use just the wrong case.

I have tried three different compilers, and all showed the problem :
 
gcc version 2.95.2 20000220 (Debian GNU/Linux)                                  (native onSA1110)    
gcc version 2.95.3 20010125 (prerelease)                                            (native on skiffclusters2.handhelds.org)  
gcc version egcs-2.91.60 19990113/philb (egcs-1.1.1 release)    (cross-compiler hosted on x86)

Russell King, the arm-linux maintainer, has kindly tried the program on several
of his machines with the following results :

> version						host	result
> ----------------------------------------------------------------------
> 2.7.2.2						arm	ok
> egcs-2.91.60 19990113/philb (egcs-1.1.1 release)	x86	bad
> egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)	arm	bad
> egcs-2.91.66 19990314/Linux (egcs-1.1.2 release)	x86	bad
> 2.95.1 19990816 (release)				arm	ok
> 2.95.2 20000220 (Debian GNU/Linux)			arm	bad
> 2.95.2 20000516 (release) [Rebel.com]			arm	ok
> 2.95.3 20010125 (prerelease)				arm	bad
> 2.95.3 20010315 (release)				x86	bad
> 2.96 20000731 (Red Hat Linux 7.1 2.96-80)		arm	ok

This problem seems to affect only arm-linux targets, both native and X86-hosted
cross, as several x86 Compilers i tried (2.95.2, 2.95.3) all worked.

I think that this problem is somehow specific to the arm-gcc, because the error
depends on whether some constants are valid arm-immediatates (<8-bit> << n*2)
or not, even though they may eventually not be used in the code after
optimization !?    

I think the key to the problem may be the expression 
      sw = 4;
      dmy * ((sw >= 5) +1);

The error occurs only if I 
 - compare sw with a constant of value <sw+1>
 - use a ">="  (not ==, <=, .. )
 - add 1 to it (not 0, 2 or any other integer)
 - multiply it with dmy (not add)


In case that might help, here is a more verbose version of the program that
might provide some additional hints:

----------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>

int global = 3;

int switch_test(void)
{

#define INITIAL_SW 0x04          /* use any positive immediate arm-constant for INITIAL_SW
                          *  (tested: 0, 1, 2, 3, 4,  10, 99, 0x0039c000) and the error occurs
                                                          * larger constants (0x101, 1000000 and negative numbers do not produce errors */

#define MUST_BE_SW_PLUS_1 (INITIAL_SW + 1)  /* this must be INITIAL_SW + 1 */


#define ONLY_1_CAUSES_ERR 1
#define ANY_INTEGER  77
#define ANY_INTEGER1 11
#define NO_ARM_IMMEDIATE 0x101 /* 257, a constant that can not be representated as an arm immediate value ( b << n*2, where b is 8 bit)*/



#define ANY_OPERATOR_EXCEPT_LOG_OR &                /* also fails for <, ==, >=, &, |, &&, but not || */

#define ANY_OF_dmy_sw_global_result sw /* use either dmy, sw, global, local */

 int dmy;
              int result;

              int sw=INITIAL_SW; /* assign any integer to sw and never overwrite it later */
            
// I could not reproduce the error without using any of the following three
statements A, B, C 


// In statement A, a comparison using any binary operator (except "||" ) seems to be required. 
// This might mean that the problem does not occur if the second operand is not
evaluated ?
      

/* A */ dmy  = (ANY_OF_dmy_sw_global_result    ANY_OPERATOR_EXCEPT_LOG_OR      ANY_INTEGER) 
                  ? ANY_INTEGER1            /* either ANY_INTEGER1 or NO_ARM_IMMEDIATE must not be a valid */
                  : NO_ARM_IMMEDIATE;  /* ARM constant (8 bit << n*2), otherwise no fault occurs */

/* In statement B, to produce the fault, I need 
 *    - ">=" Operator
 *    - compare <n> with <n+1> and then add one 
 *    - multiply ("dmy * ..", "dmy + .." will not do)
 *    - the result must be assigned to a global variable to avoid these statements 
 *        from being optimized away
 */

/* B */  global = dmy * ((sw >= MUST_BE_SW_PLUS_1) + ONLY_1_CAUSES_ERR); /* assigning to global ensures result of mult is used */ 

/* In statement C, sw is falsely evaluated only by the "switch"-operator.
 * Just assigning result = sw works correctly.
 * The error is not affected by extra case-statements or changing what the case-statements do. 
 */    

/* C */  switch( sw ) { /* only a switch causes errors, merely assigning b gives expected result */
 case INITIAL_SW: result=0;  break;  /* 0 = expected result, sw should still be = INITIAL_SW */
 default: result=99;                        /* 99 = error            */
 }
 
 return (result);  /* 0 = no error, 99 = error (values do not matter) */
}

int main(void)
{
      int res = switch_test();

      if (res == 0) {
              printf ("okay, no Error\n");
      } else {
              printf ("####### Error: result=%d #########\n", res);
      }
      return(0);
}

----------------------------------------------------------------------




Thank you very much,
Regards klaus



--
Mobotix AG
Klaus Borchers
Luxemburgerstr. 6
D-67657 Kaiserslautern
Germany

Tel: +49 (631) 3033141
Fax: +49 (631) 3033190
E-Mail: Klaus.Borchers@mobotix.com


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