是否存在逻辑上的原因GCC(4.4.7)没有直接将字节从结构移动到%eax
,还是仅仅是优化监督?
考虑以下计划:
struct foo { unsigned char x; };
struct bar { unsigned int x; };
int foo (const struct foo *x, int y) { return x->x * y; }
int bar (const struct bar *x, int y) { return x->x * y; }
使用GCC进行编译时,foo()
和bar()
的差异比预期的大得多:
foo:
.LFB0:
.cfi_startproc
movzbl (%rdi), %edx
movl %esi, %eax
imull %edx, %eax
ret
.cfi_endproc
bar:
.LFB1:
.cfi_startproc
movl (%rdi), %eax
imull %esi, %eax
ret
.cfi_endproc
我预计foo()
会像bar()
一样,除非使用不同的移动指令。
我会注意到在clang-500.2.79
下,编译器生成了我期望的foo()
代码,而foo()
和bar()
具有相同数量的指令(就像我一样)对GCC也有预期,但错了)。
答案 0 :(得分:2)
由于你在函数foo中乘以uchar x和uint y,编译器必须首先将uchar x提升为int,这是指令movzbl所做的。
请参阅the explanation of movz instructions here.
之后我用gcc 4.6.1和-O3选项重新编译了你的代码,我得到了如下组合:
foo:
.LFB34:
.cfi_startproc
movzbl (%rdi), %eax
imull %esi, %eax
ret
.cfi_endproc
bar:
.LFB35:
.cfi_startproc
movl (%rdi), %eax
imull %esi, %eax
ret
.cfi_endproc
它不再使用%edx。
答案 1 :(得分:2)
简短的回答
为什么GCC会将字复制到返回寄存器而不是字节?
因为你要求它返回一个字而不是一个字节。编译器根据您的代码完成了他们的要求。您要求在一个案例中进行大小提升,并在两个案例中签名都未签名。有不止一种方法可以做到这一点,而clang / llvm和gcc恰好有所不同。
是否存在逻辑上的原因GCC(4.4.7)没有直接将结构中的字节移动到%eax中,还是仅仅是优化疏忽?
我认为根据我们在当前编译器中看到的内容,这是一个疏忽。请参阅下面的生成代码(每种情况都使用-O2)。
与此问题相关的有趣实验。
铛
0000000000000000 <foo>:
0: 0f b6 07 movzbl (%rdi),%eax
3: 0f af c6 imul %esi,%eax
6: c3 retq
0000000000000010 <bar>:
10: 0f af 37 imul (%rdi),%esi
13: 89 f0 mov %esi,%eax
15: c3 retq
GCC
0000000000000000 <foo>:
0: 0f b6 07 movzbl (%rdi),%eax
3: 0f af c6 imul %esi,%eax
6: c3 retq
0000000000000010 <bar>:
10: 8b 07 mov (%rdi),%eax
12: 0f af c6 imul %esi,%eax
15: c3 retq
他们都生成了正确的代码。对于这个指令集上的这些小函数,指令字节数的微小差异实际上已经消失了。
当时你的编译器出于某种原因一定不能看到这种优化。
MIPS:
00000000 <foo>:
0: 90820000 lbu v0,0(a0)
4: 00000000 nop
8: 00450018 mult v0,a1
c: 00001012 mflo v0
10: 03e00008 jr ra
14: 00000000 nop
00000018 <bar>:
18: 8c820000 lw v0,0(a0)
1c: 00000000 nop
20: 00a20018 mult a1,v0
24: 00001012 mflo v0
28: 03e00008 jr ra
2c: 00000000 nop
臂
00000000 <foo>:
0: e5d00000 ldrb r0, [r0]
4: e0000091 mul r0, r1, r0
8: e12fff1e bx lr
0000000c <bar>:
c: e5900000 ldr r0, [r0]
10: e0000091 mul r0, r1, r0
14: e12fff1e bx lr
没有像x86那样的大惊喜,不同之处在于加载以及它如何处理其他24位然后代码表示它将unsigned char或int提升为有符号整数然后乘以并返回有符号整数。
另一个同样有趣的例子来补充你的问题。
struct foo { unsigned char x; };
struct bar { unsigned int x; };
char foo (const struct foo *x, char y) { return x->x * y; }
char bar (const struct bar *x, char y) { return x->x * y; }
铛
0000000000000000 <foo>:
0: 8a 07 mov (%rdi),%al
2: 40 f6 e6 mul %sil
5: 0f be c0 movsbl %al,%eax
8: c3 retq
0000000000000010 <bar>:
10: 0f af 37 imul (%rdi),%esi
13: 40 0f be c6 movsbl %sil,%eax
17: c3 retq
GCC
0000000000000000 <foo>:
0: 89 f0 mov %esi,%eax
2: f6 27 mulb (%rdi)
4: c3 retq
0000000000000010 <bar>:
10: 89 f0 mov %esi,%eax
12: f6 27 mulb (%rdi)
14: c3 retq
gcc arm
00000000 <foo>:
0: e5d00000 ldrb r0, [r0]
4: e0010190 mul r1, r0, r1
8: e20100ff and r0, r1, #255 ; 0xff
c: e12fff1e bx lr
00000010 <bar>:
10: e5900000 ldr r0, [r0]
14: e0010190 mul r1, r0, r1
18: e20100ff and r0, r1, #255 ; 0xff
1c: e12fff1e bx lr
MIPS
00000000 <foo>:
0: 90820000 lbu v0,0(a0)
4: 00052e00 sll a1,a1,0x18
8: 00052e03 sra a1,a1,0x18
c: 00a20018 mult a1,v0
10: 00001012 mflo v0
14: 00021600 sll v0,v0,0x18
18: 03e00008 jr ra
1c: 00021603 sra v0,v0,0x18
00000020 <bar>:
20: 8c820000 lw v0,0(a0)
24: 00052e00 sll a1,a1,0x18
28: 00052e03 sra a1,a1,0x18
2c: 00a20018 mult a1,v0
30: 00001012 mflo v0
34: 00021600 sll v0,v0,0x18
38: 03e00008 jr ra
3c: 00021603 sra v0,v0,0x18
该代码特别惩罚了mips。
最后
struct foo { unsigned char x; };
struct bar { unsigned int x; };
unsigned char foo (const struct foo *x, unsigned char y) { return x->x * y; }
unsigned char bar (const struct bar *x, unsigned char y) { return x->x * y; }
x86的gcc和clang使用非指定的字符生成与上面相同的代码,但是
臂
00000000 <foo>:
0: e5d00000 ldrb r0, [r0]
4: e0010190 mul r1, r0, r1
8: e20100ff and r0, r1, #255 ; 0xff
c: e12fff1e bx lr
00000010 <bar>:
10: e5900000 ldr r0, [r0]
14: e0010190 mul r1, r0, r1
18: e20100ff and r0, r1, #255 ; 0xff
1c: e12fff1e bx lr
MIPS
00000000 <foo>:
0: 90820000 lbu v0,0(a0)
4: 30a500ff andi a1,a1,0xff
8: 00a20018 mult a1,v0
c: 00001012 mflo v0
10: 03e00008 jr ra
14: 304200ff andi v0,v0,0xff
00000018 <bar>:
18: 8c820000 lw v0,0(a0)
1c: 30a500ff andi a1,a1,0xff
20: 00a20018 mult a1,v0
24: 00001012 mflo v0
28: 03e00008 jr ra
2c: 304200ff andi v0,v0,0xff
由于调用约定和指令集的组合,需要屏蔽。对这两个指令集的惩罚......当使用大小与这些指令集的寄存器大小不匹配的变量时,你会经常看到这一点。 x86具有更广泛的指令选择,x86的成本是额外逻辑成本的功率(瓦特)。
对于笑容,即使你回过头来,寄存器大小的选择也会稍微便宜一些。
00000000 <_foo>:
0: 1166 mov r5, -(sp)
2: 1185 mov sp, r5
4: 9f40 0004 movb *4(r5), r0
8: 45c0 ff00 bic $-400, r0
c: 1001 mov r0, r1
e: 7075 0006 mul 6(r5), r1
12: 1040 mov r1, r0
14: 1585 mov (sp)+, r5
16: 0087 rts pc
00000018 <_bar>:
18: 1166 mov r5, -(sp)
1a: 1185 mov sp, r5
1c: 1d41 0006 mov 6(r5), r1
20: 707d 0004 mul *4(r5), r1
24: 1040 mov r1, r0
26: 1585 mov (sp)+, r5
28: 0087 rts pc
编译器版本
gcc --version
gcc (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
clang --version
clang version 3.4 (branches/release_34 201060)
Target: x86_64-unknown-linux-gnu
Thread model: posix
arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
mips-elf-gcc --version
mips-elf-gcc (GCC) 4.8.2
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
最后一个指令集对于读者来说是一个练习,在拆卸中有一点线索......