[RFC] Re: Immediate offset in array index

Yufeng Zhang Yufeng.Zhang@arm.com
Fri Nov 29 15:32:00 GMT 2013


(Add gcc@gcc.gnu.org which is more relevant.)

After some investigation, I can answer my own question now.

On 11/11/13 16:39, Yufeng Zhang wrote:
> Hi,
>
> I'm trying to understand the following code-gen difference in the
> following code:
>
> #ifdef BEFORE
> typedef int arr_2[4][5];
> #else
> typedef int arr_2[4][4];
> #endif
>
> void foo (arr_2 a2, int i, int j)
> {
>     a2[i + 2][j] = 2;
> }
>
> The code-gen using a mainline gcc -O2 -DBEFORE is:
>
>           movslq  %esi, %rsi
>           movslq  %edx, %rdx
>           leaq    (%rsi,%rsi,4), %rax
>           leaq    40(%rdi,%rax,4), %rax
>           movl    $2, (%rax,%rdx,4)
>           ret
>
> and -O2 is:
>
>           movslq  %esi, %rsi
>           movslq  %edx, %rdx
>           addq    $2, %rsi
>           salq    $4, %rsi
>           addq    %rdi, %rsi
>           movl    $2, (%rsi,%rdx,4)
>           ret
>
> When the typedef is arr_2[4][5], the compiler distributes (i + 2) * 20
> to i * 20 + 40, while not doing the distribution when the typedef is
> arr_2[4][4].

pointer_int_sum () in c-family/c-common.c applies the distributive law 
to the non-pointer addend if the addend is a MULT_EXPR in a form of ((a 
+/- c1) * c2), where both c1 and c2 are constant immediates.  It claims 
that "this helps produce common subexpressions".

In the example above, a2[i + 2] is transformed to
   a2 + ((i * 16) + 32)
when arr_2 is typedefed as [4][4], and transformed to
   a2 + ((i * 20) + 40)
in the case of [4][5].

However, later on fold_binary_loc () in fold-const.c will revert the 
effort with the assistance from fold_plusminus_mult_expr (), which tries 
to undistribute the plus (or minus).  But for fold_plusminus_mult_expr 
() to carry out the undistribution, c2 has to be a power of two, which 
is why we see the code-gen difference (as 32 is a power of two, but 40 
is not).

> The -fdump-tree-original dumps already diff, so I guess that it is the
> parser which does something interesting.
>
> Can anyone enlighten me that which part of the parser I shall further
> look into to get a better understanding of the code-gen difference?
>
> Regards,
> Yufeng
>
> p.s.
>
> diff of the -fdump-tree-original
>
> -  (*(a2 + ((sizetype) ((long unsigned int) i * 20) + 40)))[j] = 2;
> +  (*(a2 + ((sizetype) i + 2) * 16))[j] = 2;

A similar code-gen difference can be observed in the following example code:

foo (float * a, float * b, int n, int j)
{
   *a = b[j+50] + b[j-50];
}

You'll see the following -fdump-tree-original on x86_64 -O2:

*a = *(b + ((sizetype) j + 50) * 4) + *(b + ((sizetype) ((long unsigned 
int) j * 4) + 18446744073709551416));

While b[j+50] is turned into (b + (j + 50) * 4), for b[j-50] the 
multiplication has been distributed.

The difference is caused again by fold_plusminus_mult_expr () which 
carries out the undistribution when host_integerp (200, 0) passes but 
quits early when host_integerp (18446744073709551416, 0) fails.

This looks like a classic example where different parts of a compiler 
disagree on which are better heuristics.

IMHO, array indexing expression with immediate offset(s) shall be 
distributed to allow immediates to be folded (when there are multiple) 
and re-associated to the rightmost side of the addressing expression (to 
better utilize address mode with immediate offset).  For example, given 
the following example:

typedef struct { int x,y,a,b; } X;

int
foo (X p[][4], int x, int y)
{
   return p[x+1][y+1].a;
}

the code-gen on arm -mcpu=cortex-a15 -O2 -mthumb is

         add     r2, r2, #1
         add     r1, r1, #1
         mov     r2, r2, asl #4
         add     r2, r2, r1, asl #6
         add     r0, r0, r2
         ldr     r0, [r0, #8]

which can be optimized to:

         add     r1, r0, r1, asl #6
         add     r2, r1, r2, asl #4
         ldr     r0, [r2, #88]

if the distribution is applied.  Similarly the code-gen on x86_64 can be 
optimized in a similar fashion from current poorer code-gen:

         movslq  %esi, %rsi
         addl    $1, %edx
         addq    $1, %rsi
         movslq  %edx, %rdx
         salq    $6, %rsi
         salq    $4, %rdx
         addq    %rdi, %rsi
         movl    8(%rsi,%rdx), %eax

I'm currently experimenting with a new tree-ssa pass to apply the 
distribution on a qualified addend of any POINTER_PLUS expression.  I 
deliberately avoid to change fold_binary_loc () or 
fold_plusminus_mult_expr (), as I think both folding functions are 
fairly mature and the folding can be beneficial in other cases.

I'm quite keen to hear what you think on this issue.  Any comment is 
welcomed!

Thanks,
Yufeng



More information about the Gcc-help mailing list