Bug 44474

Summary: GCC inserts redundant "test" instruction due to incorrect clobber
Product: gcc Reporter: Jason Garrett-Glaser <darkshikari>
Component: targetAssignee: Not yet assigned to anyone <unassigned>
Status: RESOLVED FIXED    
Severity: enhancement CC: astrange+gcc, gcc-bugs, jeffreyalaw
Priority: P3 Keywords: missed-optimization
Version: 4.6.0   
Target Milestone: ---   
Host: Target: i?86-*-* x86_64-*-*
Build: Known to work:
Known to fail: Last reconfirmed: 2011-12-30 00:00:00

Description Jason Garrett-Glaser 2010-06-09 04:19:54 UTC
Take the following test case:

int foo();

int test( int *b )
{
    (*b)--;
    if( *b == 0 )
        return foo();
    return 0;
}

On x86_64, with "-fomit-frame-pointer -O3 -c", gcc 4.6 compiles this to:

   0:	8b 07                	mov    eax, [rdi]
   2:	83 e8 01             	sub    eax, 0x1
   5:	85 c0                	test   eax, eax
   7:	89 07                	mov    [rdi], eax
   9:	74 05                	je     10 <test+0x10>
   b:	31 c0                	xor    eax, eax
   d:	c3                   	ret
 10:	e9 00 00 00 00       	jmp    15 <test+0x15> //foo

As can be seen, gcc inserts a redundant "test" instruction.  This problem is present in all versions of gcc I tested (3.4, 4.3.4, 4.4.1, and 4.6 SVN r160330).

According to Alexander Strange on IRC:

<astrange> the problem is that subl $1, %eax does (clobber (reg:CC 17 flags)) instead of (set (reg:CCZ 17 flags) (compare:CCZ ....
<astrange> which i don't think is fixable in a real way, but i didn't write it
Comment 1 astrange+gcc@gmail.com 2010-07-01 03:43:30 UTC
The problem is combine.

This:

int test2( int *b )
{
	int b_ = *b;
    b_--;
    if( b_ == 0 ) {
    	*b = b_;
        return foo();
    }
    *b = b_;
    return 0;
}

works:
_test2:
LFB1:
	movl	(%rdi), %eax
	decl	%eax
	je	L7 <- uses decl
	movl	%eax, (%rdi)
	xorl	%eax, %eax
	ret
	.align 4,0x90
L7:
	movl	$0, (%rdi)
	xorl	%eax, %eax
	jmp	_foo

The original turns (*b)-- into load/dec/store/cmp - combine tries to combine dec/store which fails, but doesn't try dec/cmp.
Comment 2 astrange+gcc@gmail.com 2010-08-29 06:39:08 UTC
Still happens with the new combine work (not that I really expected it to change).
Comment 3 Andrew Pinski 2011-12-30 23:50:08 UTC
Confirmed.

Combine does:
Trying 7 -> 8:
Trying 9 -> 10:
But never 7 -> 9 which is needed for this to work.
This is for:
int foo();
int t;

int test( int b )
{
    (b)--;
    t = b;
    if( b == 0 )
        return foo();
    return 0;
}
--- CUT --
Where 7, 8, 9, and 10 are:
(insn 7 3 8 2 (parallel [
            (set (reg/v:SI 60 [ b ])
                (plus:SI (reg/v:SI 62 [ b ])
                    (const_int -1 [0xffffffffffffffff])))
            (clobber (reg:CC 17 flags))
        ]) t.c:6 253 {*addsi_1}
     (expr_list:REG_DEAD (reg/v:SI 62 [ b ])
        (expr_list:REG_UNUSED (reg:CC 17 flags)
            (nil))))

(insn 8 7 9 2 (set (mem/c/i:SI (symbol_ref:DI ("t")  <var_decl 0x7fedc8e8c140 t>) [2 t+0 S4 A32])
        (reg/v:SI 60 [ b ])) t.c:7 64 {*movsi_internal}
     (nil))

(insn 9 8 10 2 (set (reg:CCZ 17 flags)
        (compare:CCZ (reg/v:SI 60 [ b ])
            (const_int 0 [0]))) t.c:8 2 {*cmpsi_ccno_1}
     (expr_list:REG_DEAD (reg/v:SI 60 [ b ])
        (nil)))

(jump_insn 10 9 11 2 (set (pc)
        (if_then_else (ne (reg:CCZ 17 flags)
                (const_int 0 [0]))
            (label_ref 16)
            (pc))) t.c:8 599 {*jcc_1}
     (expr_list:REG_DEAD (reg:CCZ 17 flags)
        (expr_list:REG_BR_PROB (const_int 6102 [0x17d6])
            (nil)))
 -> 16)
Comment 4 Jeffrey A. Law 2017-12-11 17:31:59 UTC
Compare-elimination does the right thing and eliminates the redundant test on the trunk.  Given the age of the BZ I didn't track it down any further than that.