GCC 是否优化汇编源文件?
Does GCC optimize assembly source file?
我可以使用 GCC 将汇编代码文件转换为可重新分配的文件。
gcc -c source.S -o object.o -O2
优化选项是否有效?我可以期望 GCC 优化我的汇编代码吗?
没有
GCC 通过预处理器传递您的汇编源代码,然后传递给汇编器。任何时候都不会执行任何优化。
so.s
#define HELLO 0x5
mov $HELLO, %eax
mov [=10=]x5,%eax
mov [=10=]x5,%eax
mov [=10=]x5,%eax
retq
gcc -O2 -c so.s -o so.o
objdump -d so.o
0000000000000000 <.text>:
0: b8 00 00 00 00 mov [=10=]x0,%eax
5: b8 05 00 00 00 mov [=10=]x5,%eax
a: b8 05 00 00 00 mov [=10=]x5,%eax
f: b8 05 00 00 00 mov [=10=]x5,%eax
14: c3 retq
它甚至没有 pre-process 定义。
将 so.s 重命名为 so.S
gcc -O2 -c so.S -o so.o
objdump -d so.o
0000000000000000 <.text>:
0: b8 05 00 00 00 mov [=11=]x5,%eax
5: b8 05 00 00 00 mov [=11=]x5,%eax
a: b8 05 00 00 00 mov [=11=]x5,%eax
f: b8 05 00 00 00 mov [=11=]x5,%eax
14: c3 retq
它 pre-process 是定义,但没有发生优化。
稍微深入一点,传递给什么
gcc -O2 -c -save-temps so.s -o so.o
[0][as]
[1][--64]
[2][-o]
[3][so.o]
[4][so.s]
cat so.s
#define HELLO 0x5
mov $HELLO, %eax
mov [=12=]x5,%eax
mov [=12=]x5,%eax
mov [=12=]x5,%eax
retq
和
gcc -O2 -c -save-temps so.S -o so.o
[0][as]
[1][--64]
[2][-o]
[3][so.o]
[4][so.s]
cat so.s
# 1 "so.S"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "so.S"
mov [=13=]x5, %eax
mov [=13=]x5,%eax
mov [=13=]x5,%eax
mov [=13=]x5,%eax
retq
仍然没有优化。
演示应该绰绰有余。您可以进行 link 时间优化,您必须正确构建对象,然后告诉 link 人员。但我怀疑它不是在机器代码级别而是在高级 re-generates 代码。
int main ( void )
{
return(5);
}
gcc -O2 so.c -save-temps -o so.o
cat so.s
.file "so.c"
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.section .text.startup,"ax",@progbits
.LHOTB0:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
movl , %eax
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .text.unlikely
.LCOLDE0:
.section .text.startup
.LHOTE0:
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
使用上面的so.S
gcc -flto -O2 so.S -save-temps -o so.o
cat so.s
# 1 "so.S"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "so.S"
mov [=15=]x5, %eax
mov [=15=]x5,%eax
mov [=15=]x5,%eax
mov [=15=]x5,%eax
retq
使用上面的so.c
gcc -flto -O2 so.c -save-temps -o so.o
cat so.s
.file "so.c"
.section .gnu.lto_.profile.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d`a`"
.string "2L4"
.string ""
.string "o"
.ascii "6"
.text
.section .gnu.lto_.icf.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d"
.string "[=16=]16[=16=]6[=16=]4`d0|67Nv[=16=]6"
.ascii "73[=16=]3I"
.text
.section .gnu.lto_.jmpfuncs.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d"
.string "[=16=]1V[=16=]6[=16=]4"
.string "3"
.string ""
.string ""
.string "6"
.ascii "\f"
.text
.section .gnu.lto_.inline.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d"
.string "[=16=]11[=16=]6[=16=]4"
.string "203120[=16=]11l3410b"
.string "\n1"
.ascii "2"
.text
.section .gnu.lto_.pureconst.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d`f`"
.string "2\f"
.string ""
.string "X"
.ascii "\n"
.text
.section .gnu.lto_main.3f5dbe2a70110b8,"e",@progbits
.ascii "x45636[=16=]1a057473606"
.ascii "B310^1[=16=]3(<060B42[=16=]510r3-"
.ascii "4[56\n[=16=]5170\n1NH([=16=]43&9191o.61"
.ascii "4f240 2!p0'jz\fha=0701bkp\b6c3"
.ascii "46`603nt61[=16=]5Jb/Qo0rl%6367"
.ascii "\r11L-17(b22^01L26V7A65([RD"
.ascii ":s443E517o3&q6e23H7y0k6W42"
.ascii "2`357155p10766204"
.ascii "30Wj4[=16=]3\t0<\r"
.text
.section .gnu.lto_.symbol_nodes.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d0f"
.string "[=16=]2&6z[=16=]66\t70@466@0\b"
.ascii "'0[=16=]4[=16=]2"
.text
.section .gnu.lto_.refs.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`[=16=]4B "
.string ""
.string ""
.string "9"
.ascii "[=16=]7"
.text
.section .gnu.lto_.decls.3f5dbe2a70110b8,"e",@progbits
.string "x45PMK[=16=]2Q4575h21R-\00752407A5b6A431p1AmZ^70DB0N[=16=]4)0j~A\bA11[=16=]7J!1e7@\b46y36023735w697]3@222V%73O444[=16=]3\nM3\2k2g1/170;7012w{0510521D22[=16=]4xC045653423460"
.ascii "53\nJ_20u74I`[=16=]11O53i[=16=]62tB3"
.ascii "\b0X37Se[=16=]57h600602q73A"
.ascii "477<677002s311T2[#Q41"
.ascii "6345812S35+16077%[=16=]4"
.ascii "56[52613715v6+7|14"
.ascii "\n16;?57x2Z7424[=16=]64yl40"
.ascii "621[=16=]7$%61[=16=]69~6V43d75Q5U5"
.ascii "07GS25;126Y=14230[=16=]25"
.ascii "\fd`7123(47234;.UJg5\"1'7"
.ascii "5Jlgw/553Q444[4_07k~"
.text
.section .gnu.lto_.symtab.3f5dbe2a70110b8,"e",@progbits
.string "main"
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string "0"
.string ""
.string ""
.text
.section .gnu.lto_.opts,"e",@progbits
.string "'-fmath-errno' '-fsigned-zeros' '-ftrapping-math' '-fno-trapv' '-fno-openmp' '-fno-openacc' '-mtune=generic' '-march=x86-64' '-O2' '-flto' '-fstack-protector-strong'"
.text
.comm __gnu_lto_v1,1,1
.comm __gnu_lto_slim,1,1
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
因此,gcc 似乎仍然没有进行任何优化,删除了这些没有功能优势且基本上是死代码的重复指令。它确实表明,如果文件有 .S,gcc 将 pre-process 代码,但如果是 .s 则不会(可以试验或阅读其他 .asm 的文档?)。这些是 运行 on linux,gcc 是 gcc,binutils 是 binutils,具体的文件名扩展敏感度可能因目标主机而异。
link 时间优化似乎与高级代码有关,因为人们不希望与汇编语言代码有关。人们期望 link 时间优化基于中端代码而不是后端。
我们知道 gcc 不是汇编程序,它只是传递它,即使它是从 C 生成的,它也会传递它,所以它需要一个汇编程序解析器,然后逻辑来处理该语言,然后挑选出要处理的东西继续 link 时间优化。
您可以阅读有关 link 时间优化的更多信息,看看是否有办法将其应用于汇编器...我假设不会,但您的整个问题是关于如何使用这些工具以及如何使用这些工具他们工作。
汇编语言优化不一定是一回事,这就是重点,现在有伪指令的伪代码,汇编器可以选择优化的实现
ldr r0,=0x12345678
ldr r0,=0x1000
ldr r0,=0xFFFFFF12
00000000 <.text>:
0: e59f0004 ldr r0, [pc, #4] ; c <.text+0xc>
4: e3a00a01 mov r0, #4096 ; 0x1000
8: e3e000ed mvn r0, #237 ; 0xed
c: 12345678 .word 0x12345678
但那是伪代码,所以支持它的汇编程序可以自由地做他们想做的事。 (汇编程序定义汇编语言(而不是目标),因此根据定义他们可以做任何他们想做的事)。在该注释中,当工具链也有一个汇编程序时,使用编译器作为汇编程序会将其更改为另一种汇编语言,因为汇编语言是由该工具定义的。因此,当您允许 gcc pre-process 代码时,您基本上是在使用与 as 不同的汇编语言。就像编译器的内联汇编是另一种汇编语言一样。 gnu 工具链的每个目标至少三种汇编语言。
如果您不想 hand-optimize 您的 asm,汇编语言是您错误的源语言选择。也许考虑 LLVM-IR 如果你想要一些 asm-like 但它实际上是优化编译器的输入。 (还有 ISA-independent。)
公平地说,有 一些 binary-to-binary 重新编译器/优化器试图找出什么是实现细节和什么是重要的逻辑,并相应地进行优化。 (从 asm 源代码而不是机器代码读取也是可能的;asm 和机器代码很容易来回转换,并且具有接近 1:1 的映射)。但这不是汇编程序所做的。
汇编程序的工作通常只是忠实地将您编写的内容翻译成 asm。有一个工具可以做到这一点,对于实验找出实际上 是什么 是必要的,而无需手动编写实际机器代码的烦恼。
有趣的是 GAS,GNU 汇编器 确实 有一些 GCC 没有启用的 x86 有限优化选项 front-end,即使你的 运行 gcc -O2
。 (您可以 运行 gcc -v ...
查看 front-end 如何调用其他程序来完成实际工作,有哪些选项。)
使用 gcc -Wa,-Os -O3 foo.c bar.S
启用对您的 C 的全面优化,以及 GAS 对您的 asm 的小窥孔优化。(或 -Wa,-O2
,不幸的是手册是错误并且 -Os
错过了 -O2
中的一些优化) -Wa,...
在 as
命令行上传递 ...
,就像 -Wl,...
传递链接器选项一样通过 GCC front-end.
GCC 通常不会启用 as
的优化,因为它通常会提供 GAS already-optimized asm.
GAS 的优化仅针对隔离的单个指令,因此只有当一条指令可以被另一条指令替换时 完全相同的架构效果(长度除外,因此对 RIP 的影响不同)。 micro——架构效果(性能)也可以不同;这就是 non-size 优化的重点。
来自 as(1)
man page,因此请注意这些是 as
个选项,不是 gcc
个选项。
-O0 | -O | -O1 | -O2 | -Os
Optimize instruction encoding with smaller instruction size. -O
and -O1
encode 64-bit register load instructions with 64-bit
immediate as 32-bit register load instructions with 31-bit or
32-bits immediates, encode 64-bit register clearing instructions
with 32-bit register clearing instructions, encode 256-bit/512-bit
VEX/EVEX vector register clearing instructions with 128-bit VEX
vector register clearing instructions, encode 128-bit/256-bit EVEX
vector register load/store instructions with VEX vector register
load/store instructions, and encode 128-bit/256-bit EVEX packed
integer logical instructions with 128-bit/256-bit VEX packed
integer logical.
-O2
includes -O1
optimization plus encodes 256-bit/512-bit EVEX
vector register clearing instructions with 128-bit EVEX vector
register clearing instructions. In 64-bit mode VEX encoded
instructions with commutative source operands will also have their
source operands swapped if this allows using the 2-byte VEX prefix
form instead of the 3-byte one. Certain forms of AND as well as OR
with the same (register) operand specified twice will also be
changed to TEST.
-Os
includes -O2
optimization plus encodes 16-bit, 32-bit and
64-bit register tests with immediate as 8-bit register test with
immediate. -O0
turns off this optimization.
(回复:其中一些 VEX / EVEX operand-size 和 code-size 优化: and the section near the end of my answer on How to tell the length of an x86 instruction? 回复:2 与 3 字节 VEX 前缀)
不幸的是 -O2
和 -Os
冲突,-Os
实际上并不包含 -O2
中的所有内容。你不能让它优化 test [re]dx, 1
到 test dl,1
(-Os
) 和 优化 or al,al
到 test al,al
( -O2
).
但它仍然比 NASM 更优化。 (NASM 的优化默认是开启的,古代版本除外;GAS 的优化默认是关闭的,除了在不改变助记符或操作数名称的情况下选择最短的编码。)
test r/m32, imm8
不可编码,因此 edx 版本需要 imm32。
or al,al
是一个过时的 8080 习语,即 ,除了有时在 P6 系列上以避免 register-read 停顿,而故意 re-writing 寄存器实际上 更好 而不是避免延长 dep 链。
.intel_syntax noprefix
shufps xmm0, xmm0, 0
vxorps zmm31, zmm31, zmm31
vxorps zmm1, zmm1, zmm1
vxorps ymm15, ymm15, ymm15
vpxord zmm15, zmm15, zmm15
vpxord ymm3, ymm14, ymm15
vpxord ymm3, ymm4, ymm15
vmovd xmm16, [rdi + 256] # can use EVEX scaled disp8
vmovd xmm0, [rdi + 256] # could use EVEX scaled disp8 but doesn't even with a -march enabling AVX512
xor rax, rax
or al,al
cmp dl, 0
test rdx, 1
mov rax, 1
mov rax, -1
mov rax, 0xffffffff80000000
.att_syntax
movabs $-1, %rax
movq , %rax
movabs , %rax
与 gcc -g -Wa,-msse2avx -Wa,-O2 -Wa,-march=znver2+avx512dq+avx512vl -c foo.s
组装(出于某些疯狂的原因,as
对现代 AMD CPU 名称具有 -march=
支持,但对英特尔仅支持 corei7
和一些 Xeon Phi,而不是像 GCC 那样的 Skylake-avx512。所以我不得不手动启用 AVX512 来测试它。
objdump -dwrC -Mintel -S
源码+反汇编
0000000000000000 <.text>:
.intel_syntax noprefix
shufps xmm0, xmm0, 0 # -msse2avx just for fun
0: c5 f8 c6 c0 00 vshufps xmm0,xmm0,xmm0,0x0
vxorps zmm31, zmm31, zmm31 # avoids triggering AVX512 frequency limit
5: 62 01 04 00 57 ff vxorps xmm31,xmm31,xmm31
vxorps zmm1, zmm1, zmm1 # shorter, using VEX
b: c5 f0 57 c9 vxorps xmm1,xmm1,xmm1
vxorps ymm15, ymm15, ymm15 # missed optimization, could vxorps xmm15, xmm0, xmm0 for a 2-byte VEX and still be a zeroing idiom
f: c4 41 00 57 ff vxorps xmm15,xmm15,xmm15
vpxord zmm15, zmm15, zmm15 # AVX512 mnemonic optimized to AVX1, same missed opt for source operands.
14: c4 41 01 ef ff vpxor xmm15,xmm15,xmm15
vpxord ymm3, ymm14, ymm15 # no optimization possible
19: c4 c1 0d ef df vpxor ymm3,ymm14,ymm15
vpxord ymm3, ymm4, ymm15 # reversed operands to allow 2-byte VEX
1e: c5 85 ef dc vpxor ymm3,ymm15,ymm4
vmovd xmm16, [rdi + 256] # uses EVEX scaled disp8 because xmm16 requires EVEX anyway
22: 62 e1 7d 08 6e 47 40 vmovd xmm16,DWORD PTR [rdi+0x100]
vmovd xmm0, [rdi + 256] # could use EVEX scaled disp8 but doesn't even with a -march enabling AVX512
29: c5 f9 6e 87 00 01 00 00 vmovd xmm0,DWORD PTR [rdi+0x100]
xor rax, rax # dropped REX prefix
31: 31 c0 xor eax,eax
or al,al
33: 84 c0 test al,al
cmp dl, 0 # optimization to test dl,dl not quite legal: different effect on AF
35: 80 fa 00 cmp dl,0x0
test rdx, 1 # partial optimization: only to 32-bit, not 8-bit
38: f7 c2 01 00 00 00 test edx,0x1
mov rax, 1
3e: b8 01 00 00 00 mov eax,0x1
mov rax, -1 # sign-extension required
43: 48 c7 c0 ff ff ff ff mov rax,0xffffffffffffffff
mov rax, 0xffffffff80000000
4a: 48 c7 c0 00 00 00 80 mov rax,0xffffffff80000000
.att_syntax
movabs $-1, %rax # movabs forces imm64, despite -O2
51: 48 b8 ff ff ff ff ff ff ff ff movabs rax,0xffffffffffffffff
movq , %rax # but explicit q operand size doesn't stop opt
5b: b8 01 00 00 00 mov eax,0x1
movabs , %rax
60: 48 b8 01 00 00 00 00 00 00 00 movabs rax,0x1
因此,不幸的是,即使在不需要 EVEX 的情况下,即使明确启用 AVX512VL 和 AVX512DQ 也无法让 GAS 为 vmovd
选择更短的 EVEX 编码。这可能仍然是故意的:您可能希望 some 函数使用 AVX512,而一些函数则避免它。如果您使用 ISA-option 限制来捕获对 ISA 扩展的意外使用,则必须为整个此类文件启用 AVX512。在您意想不到的地方发现使用 EVEX 的汇编程序可能会令人惊讶。
您可以使用 {evex} vmovd xmm0, [rdi + 256]
手动强制执行。 (不幸的是,GCC 在编译 C 时并没有这样做,-march=skylake-avx512
确实 确实 允许它在任何地方自由使用 AVX512 指令。)
我可以使用 GCC 将汇编代码文件转换为可重新分配的文件。
gcc -c source.S -o object.o -O2
优化选项是否有效?我可以期望 GCC 优化我的汇编代码吗?
没有
GCC 通过预处理器传递您的汇编源代码,然后传递给汇编器。任何时候都不会执行任何优化。
so.s
#define HELLO 0x5
mov $HELLO, %eax
mov [=10=]x5,%eax
mov [=10=]x5,%eax
mov [=10=]x5,%eax
retq
gcc -O2 -c so.s -o so.o
objdump -d so.o
0000000000000000 <.text>:
0: b8 00 00 00 00 mov [=10=]x0,%eax
5: b8 05 00 00 00 mov [=10=]x5,%eax
a: b8 05 00 00 00 mov [=10=]x5,%eax
f: b8 05 00 00 00 mov [=10=]x5,%eax
14: c3 retq
它甚至没有 pre-process 定义。
将 so.s 重命名为 so.S
gcc -O2 -c so.S -o so.o
objdump -d so.o
0000000000000000 <.text>:
0: b8 05 00 00 00 mov [=11=]x5,%eax
5: b8 05 00 00 00 mov [=11=]x5,%eax
a: b8 05 00 00 00 mov [=11=]x5,%eax
f: b8 05 00 00 00 mov [=11=]x5,%eax
14: c3 retq
它 pre-process 是定义,但没有发生优化。
稍微深入一点,传递给什么
gcc -O2 -c -save-temps so.s -o so.o
[0][as]
[1][--64]
[2][-o]
[3][so.o]
[4][so.s]
cat so.s
#define HELLO 0x5
mov $HELLO, %eax
mov [=12=]x5,%eax
mov [=12=]x5,%eax
mov [=12=]x5,%eax
retq
和
gcc -O2 -c -save-temps so.S -o so.o
[0][as]
[1][--64]
[2][-o]
[3][so.o]
[4][so.s]
cat so.s
# 1 "so.S"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "so.S"
mov [=13=]x5, %eax
mov [=13=]x5,%eax
mov [=13=]x5,%eax
mov [=13=]x5,%eax
retq
仍然没有优化。
演示应该绰绰有余。您可以进行 link 时间优化,您必须正确构建对象,然后告诉 link 人员。但我怀疑它不是在机器代码级别而是在高级 re-generates 代码。
int main ( void )
{
return(5);
}
gcc -O2 so.c -save-temps -o so.o
cat so.s
.file "so.c"
.section .text.unlikely,"ax",@progbits
.LCOLDB0:
.section .text.startup,"ax",@progbits
.LHOTB0:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
movl , %eax
ret
.cfi_endproc
.LFE0:
.size main, .-main
.section .text.unlikely
.LCOLDE0:
.section .text.startup
.LHOTE0:
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
使用上面的so.S
gcc -flto -O2 so.S -save-temps -o so.o
cat so.s
# 1 "so.S"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "so.S"
mov [=15=]x5, %eax
mov [=15=]x5,%eax
mov [=15=]x5,%eax
mov [=15=]x5,%eax
retq
使用上面的so.c
gcc -flto -O2 so.c -save-temps -o so.o
cat so.s
.file "so.c"
.section .gnu.lto_.profile.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d`a`"
.string "2L4"
.string ""
.string "o"
.ascii "6"
.text
.section .gnu.lto_.icf.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d"
.string "[=16=]16[=16=]6[=16=]4`d0|67Nv[=16=]6"
.ascii "73[=16=]3I"
.text
.section .gnu.lto_.jmpfuncs.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d"
.string "[=16=]1V[=16=]6[=16=]4"
.string "3"
.string ""
.string ""
.string "6"
.ascii "\f"
.text
.section .gnu.lto_.inline.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d"
.string "[=16=]11[=16=]6[=16=]4"
.string "203120[=16=]11l3410b"
.string "\n1"
.ascii "2"
.text
.section .gnu.lto_.pureconst.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d`f`"
.string "2\f"
.string ""
.string "X"
.ascii "\n"
.text
.section .gnu.lto_main.3f5dbe2a70110b8,"e",@progbits
.ascii "x45636[=16=]1a057473606"
.ascii "B310^1[=16=]3(<060B42[=16=]510r3-"
.ascii "4[56\n[=16=]5170\n1NH([=16=]43&9191o.61"
.ascii "4f240 2!p0'jz\fha=0701bkp\b6c3"
.ascii "46`603nt61[=16=]5Jb/Qo0rl%6367"
.ascii "\r11L-17(b22^01L26V7A65([RD"
.ascii ":s443E517o3&q6e23H7y0k6W42"
.ascii "2`357155p10766204"
.ascii "30Wj4[=16=]3\t0<\r"
.text
.section .gnu.lto_.symbol_nodes.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`d0f"
.string "[=16=]2&6z[=16=]66\t70@466@0\b"
.ascii "'0[=16=]4[=16=]2"
.text
.section .gnu.lto_.refs.3f5dbe2a70110b8,"e",@progbits
.string "x4ca`[=16=]4B "
.string ""
.string ""
.string "9"
.ascii "[=16=]7"
.text
.section .gnu.lto_.decls.3f5dbe2a70110b8,"e",@progbits
.string "x45PMK[=16=]2Q4575h21R-\00752407A5b6A431p1AmZ^70DB0N[=16=]4)0j~A\bA11[=16=]7J!1e7@\b46y36023735w697]3@222V%73O444[=16=]3\nM3\2k2g1/170;7012w{0510521D22[=16=]4xC045653423460"
.ascii "53\nJ_20u74I`[=16=]11O53i[=16=]62tB3"
.ascii "\b0X37Se[=16=]57h600602q73A"
.ascii "477<677002s311T2[#Q41"
.ascii "6345812S35+16077%[=16=]4"
.ascii "56[52613715v6+7|14"
.ascii "\n16;?57x2Z7424[=16=]64yl40"
.ascii "621[=16=]7$%61[=16=]69~6V43d75Q5U5"
.ascii "07GS25;126Y=14230[=16=]25"
.ascii "\fd`7123(47234;.UJg5\"1'7"
.ascii "5Jlgw/553Q444[4_07k~"
.text
.section .gnu.lto_.symtab.3f5dbe2a70110b8,"e",@progbits
.string "main"
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string ""
.string "0"
.string ""
.string ""
.text
.section .gnu.lto_.opts,"e",@progbits
.string "'-fmath-errno' '-fsigned-zeros' '-ftrapping-math' '-fno-trapv' '-fno-openmp' '-fno-openacc' '-mtune=generic' '-march=x86-64' '-O2' '-flto' '-fstack-protector-strong'"
.text
.comm __gnu_lto_v1,1,1
.comm __gnu_lto_slim,1,1
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
因此,gcc 似乎仍然没有进行任何优化,删除了这些没有功能优势且基本上是死代码的重复指令。它确实表明,如果文件有 .S,gcc 将 pre-process 代码,但如果是 .s 则不会(可以试验或阅读其他 .asm 的文档?)。这些是 运行 on linux,gcc 是 gcc,binutils 是 binutils,具体的文件名扩展敏感度可能因目标主机而异。
link 时间优化似乎与高级代码有关,因为人们不希望与汇编语言代码有关。人们期望 link 时间优化基于中端代码而不是后端。
我们知道 gcc 不是汇编程序,它只是传递它,即使它是从 C 生成的,它也会传递它,所以它需要一个汇编程序解析器,然后逻辑来处理该语言,然后挑选出要处理的东西继续 link 时间优化。
您可以阅读有关 link 时间优化的更多信息,看看是否有办法将其应用于汇编器...我假设不会,但您的整个问题是关于如何使用这些工具以及如何使用这些工具他们工作。
汇编语言优化不一定是一回事,这就是重点,现在有伪指令的伪代码,汇编器可以选择优化的实现
ldr r0,=0x12345678
ldr r0,=0x1000
ldr r0,=0xFFFFFF12
00000000 <.text>:
0: e59f0004 ldr r0, [pc, #4] ; c <.text+0xc>
4: e3a00a01 mov r0, #4096 ; 0x1000
8: e3e000ed mvn r0, #237 ; 0xed
c: 12345678 .word 0x12345678
但那是伪代码,所以支持它的汇编程序可以自由地做他们想做的事。 (汇编程序定义汇编语言(而不是目标),因此根据定义他们可以做任何他们想做的事)。在该注释中,当工具链也有一个汇编程序时,使用编译器作为汇编程序会将其更改为另一种汇编语言,因为汇编语言是由该工具定义的。因此,当您允许 gcc pre-process 代码时,您基本上是在使用与 as 不同的汇编语言。就像编译器的内联汇编是另一种汇编语言一样。 gnu 工具链的每个目标至少三种汇编语言。
如果您不想 hand-optimize 您的 asm,汇编语言是您错误的源语言选择。也许考虑 LLVM-IR 如果你想要一些 asm-like 但它实际上是优化编译器的输入。 (还有 ISA-independent。)
公平地说,有 一些 binary-to-binary 重新编译器/优化器试图找出什么是实现细节和什么是重要的逻辑,并相应地进行优化。 (从 asm 源代码而不是机器代码读取也是可能的;asm 和机器代码很容易来回转换,并且具有接近 1:1 的映射)。但这不是汇编程序所做的。
汇编程序的工作通常只是忠实地将您编写的内容翻译成 asm。有一个工具可以做到这一点,对于实验找出实际上 是什么 是必要的,而无需手动编写实际机器代码的烦恼。
有趣的是 GAS,GNU 汇编器 确实 有一些 GCC 没有启用的 x86 有限优化选项 front-end,即使你的 运行 gcc -O2
。 (您可以 运行 gcc -v ...
查看 front-end 如何调用其他程序来完成实际工作,有哪些选项。)
使用 gcc -Wa,-Os -O3 foo.c bar.S
启用对您的 C 的全面优化,以及 GAS 对您的 asm 的小窥孔优化。(或 -Wa,-O2
,不幸的是手册是错误并且 -Os
错过了 -O2
中的一些优化) -Wa,...
在 as
命令行上传递 ...
,就像 -Wl,...
传递链接器选项一样通过 GCC front-end.
GCC 通常不会启用 as
的优化,因为它通常会提供 GAS already-optimized asm.
GAS 的优化仅针对隔离的单个指令,因此只有当一条指令可以被另一条指令替换时 完全相同的架构效果(长度除外,因此对 RIP 的影响不同)。 micro——架构效果(性能)也可以不同;这就是 non-size 优化的重点。
来自 as(1)
man page,因此请注意这些是 as
个选项,不是 gcc
个选项。
-O0 | -O | -O1 | -O2 | -Os
Optimize instruction encoding with smaller instruction size.
-O
and-O1
encode 64-bit register load instructions with 64-bit immediate as 32-bit register load instructions with 31-bit or 32-bits immediates, encode 64-bit register clearing instructions
with 32-bit register clearing instructions, encode 256-bit/512-bit VEX/EVEX vector register clearing instructions with 128-bit VEX vector register clearing instructions, encode 128-bit/256-bit EVEX vector register load/store instructions with VEX vector register load/store instructions, and encode 128-bit/256-bit EVEX packed integer logical instructions with 128-bit/256-bit VEX packed integer logical.
-O2
includes-O1
optimization plus encodes 256-bit/512-bit EVEX vector register clearing instructions with 128-bit EVEX vector register clearing instructions. In 64-bit mode VEX encoded instructions with commutative source operands will also have their source operands swapped if this allows using the 2-byte VEX prefix form instead of the 3-byte one. Certain forms of AND as well as OR with the same (register) operand specified twice will also be changed to TEST.
-Os
includes-O2
optimization plus encodes 16-bit, 32-bit and
64-bit register tests with immediate as 8-bit register test with
immediate.-O0
turns off this optimization.
(回复:其中一些 VEX / EVEX operand-size 和 code-size 优化:
不幸的是 -O2
和 -Os
冲突,-Os
实际上并不包含 -O2
中的所有内容。你不能让它优化 test [re]dx, 1
到 test dl,1
(-Os
) 和 优化 or al,al
到 test al,al
( -O2
).
但它仍然比 NASM 更优化。 (NASM 的优化默认是开启的,古代版本除外;GAS 的优化默认是关闭的,除了在不改变助记符或操作数名称的情况下选择最短的编码。)
test r/m32, imm8
不可编码,因此 edx 版本需要 imm32。
or al,al
是一个过时的 8080 习语,即
.intel_syntax noprefix
shufps xmm0, xmm0, 0
vxorps zmm31, zmm31, zmm31
vxorps zmm1, zmm1, zmm1
vxorps ymm15, ymm15, ymm15
vpxord zmm15, zmm15, zmm15
vpxord ymm3, ymm14, ymm15
vpxord ymm3, ymm4, ymm15
vmovd xmm16, [rdi + 256] # can use EVEX scaled disp8
vmovd xmm0, [rdi + 256] # could use EVEX scaled disp8 but doesn't even with a -march enabling AVX512
xor rax, rax
or al,al
cmp dl, 0
test rdx, 1
mov rax, 1
mov rax, -1
mov rax, 0xffffffff80000000
.att_syntax
movabs $-1, %rax
movq , %rax
movabs , %rax
与 gcc -g -Wa,-msse2avx -Wa,-O2 -Wa,-march=znver2+avx512dq+avx512vl -c foo.s
组装(出于某些疯狂的原因,as
对现代 AMD CPU 名称具有 -march=
支持,但对英特尔仅支持 corei7
和一些 Xeon Phi,而不是像 GCC 那样的 Skylake-avx512。所以我不得不手动启用 AVX512 来测试它。
objdump -dwrC -Mintel -S
源码+反汇编
0000000000000000 <.text>:
.intel_syntax noprefix
shufps xmm0, xmm0, 0 # -msse2avx just for fun
0: c5 f8 c6 c0 00 vshufps xmm0,xmm0,xmm0,0x0
vxorps zmm31, zmm31, zmm31 # avoids triggering AVX512 frequency limit
5: 62 01 04 00 57 ff vxorps xmm31,xmm31,xmm31
vxorps zmm1, zmm1, zmm1 # shorter, using VEX
b: c5 f0 57 c9 vxorps xmm1,xmm1,xmm1
vxorps ymm15, ymm15, ymm15 # missed optimization, could vxorps xmm15, xmm0, xmm0 for a 2-byte VEX and still be a zeroing idiom
f: c4 41 00 57 ff vxorps xmm15,xmm15,xmm15
vpxord zmm15, zmm15, zmm15 # AVX512 mnemonic optimized to AVX1, same missed opt for source operands.
14: c4 41 01 ef ff vpxor xmm15,xmm15,xmm15
vpxord ymm3, ymm14, ymm15 # no optimization possible
19: c4 c1 0d ef df vpxor ymm3,ymm14,ymm15
vpxord ymm3, ymm4, ymm15 # reversed operands to allow 2-byte VEX
1e: c5 85 ef dc vpxor ymm3,ymm15,ymm4
vmovd xmm16, [rdi + 256] # uses EVEX scaled disp8 because xmm16 requires EVEX anyway
22: 62 e1 7d 08 6e 47 40 vmovd xmm16,DWORD PTR [rdi+0x100]
vmovd xmm0, [rdi + 256] # could use EVEX scaled disp8 but doesn't even with a -march enabling AVX512
29: c5 f9 6e 87 00 01 00 00 vmovd xmm0,DWORD PTR [rdi+0x100]
xor rax, rax # dropped REX prefix
31: 31 c0 xor eax,eax
or al,al
33: 84 c0 test al,al
cmp dl, 0 # optimization to test dl,dl not quite legal: different effect on AF
35: 80 fa 00 cmp dl,0x0
test rdx, 1 # partial optimization: only to 32-bit, not 8-bit
38: f7 c2 01 00 00 00 test edx,0x1
mov rax, 1
3e: b8 01 00 00 00 mov eax,0x1
mov rax, -1 # sign-extension required
43: 48 c7 c0 ff ff ff ff mov rax,0xffffffffffffffff
mov rax, 0xffffffff80000000
4a: 48 c7 c0 00 00 00 80 mov rax,0xffffffff80000000
.att_syntax
movabs $-1, %rax # movabs forces imm64, despite -O2
51: 48 b8 ff ff ff ff ff ff ff ff movabs rax,0xffffffffffffffff
movq , %rax # but explicit q operand size doesn't stop opt
5b: b8 01 00 00 00 mov eax,0x1
movabs , %rax
60: 48 b8 01 00 00 00 00 00 00 00 movabs rax,0x1
因此,不幸的是,即使在不需要 EVEX 的情况下,即使明确启用 AVX512VL 和 AVX512DQ 也无法让 GAS 为 vmovd
选择更短的 EVEX 编码。这可能仍然是故意的:您可能希望 some 函数使用 AVX512,而一些函数则避免它。如果您使用 ISA-option 限制来捕获对 ISA 扩展的意外使用,则必须为整个此类文件启用 AVX512。在您意想不到的地方发现使用 EVEX 的汇编程序可能会令人惊讶。
您可以使用 {evex} vmovd xmm0, [rdi + 256]
手动强制执行。 (不幸的是,GCC 在编译 C 时并没有这样做,-march=skylake-avx512
确实 确实 允许它在任何地方自由使用 AVX512 指令。)