【笔记】汇编入门
鉴于在华子呆着没事干,开始学习汇编。
简单循环
int test(int a, int b) {
for (int i = 0; i < b; ++i) {
a <<= 1;
}
return a;
}
O0
.file "a.cpp"
.text
.globl _Z4testii ; ii 表示接受两个整型参数
.type _Z4testii, @function
_Z4testii:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl %edi, -20(%rbp) ; 将 a 保存到栈位置 -20(%rbp)
movl %esi, -24(%rbp) ; 将 b 保存到栈位置 -24(%rbp)
movl $0, -4(%rbp) ; 初始化 i = 0
jmp .L2
.L3:
sall -20(%rbp) ; a <<= 1
addl $1, -4(%rbp) ; i++
.L2:
movl -4(%rbp), %eax ; 将循环计数器加载到 EAX
cmpl -24(%rbp), %eax ; 比较 i 和第二个参数 b
jl .L3 ; 如果 i < b,跳转到循环体
movl -20(%rbp), %eax ; 将 a 加载到 EAX(返回值寄存器)
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z4testii, .-_Z4testii
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
这段代码没有进行优化(可能是使用 -O0 编译选项),因此:
- 内存访问频繁: 所有操作都通过内存进行,而不是直接使用寄存器
- 栈帧完整: 创建了完整的栈帧,包括基指针的保存和恢复
- 循环效率低: 每次循环都需要从内存加载和存储值
O1
.file "a.cpp"
.text
.globl _Z4testii
.type _Z4testii, @function
_Z4testii:
.LFB0:
.cfi_startproc
endbr64
movl %edi, %eax ; 将 a 移动到 EAX(结果寄存器)
testl %esi, %esi ; 测试 b 的值. testl指令,是将两个操作数做与来设置零标志位和负数标识,常用的方法是testl %eax,%eax来检查%eax是正数负数还是0
jle .L2 ; 如果 b <= 0,跳转到.L2(直接返回)
movl $0, %edx ; 将循环计数器 EDX 初始化为0
.L3:
addl %eax, %eax ; a = a + a
addl $1, %edx ; i++
cmpl %edx, %esi ; 比较 i 和 b
jne .L3 ; 如果不相等就继续循环
.L2:
ret
.cfi_endproc
.LFE0:
.size _Z4testii, .-_Z4testii
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
寄存器分配优化:
- 使用EAX同时存储参数和结果,减少数据移动
- 使用EDX作为循环计数器,避免内存访问
循环优化:
- 使用do-while循环结构,减少一次条件判断
- 使用计数器与参数直接比较,而不是与0比较
算术优化:
- 使用addl %eax, %eax实现乘以2,而不是使用移位指令
- 这可能是因为在某些处理器上,加法比移位更快
栈帧消除:
- 没有创建栈帧(没有pushq %rbp和movq %rsp, %rbp)
- 所有操作都在寄存器中完成,极大提高效率
O2
.file "a.cpp"
.text
.p2align 4
.globl _Z4testii
.type _Z4testii, @function
_Z4testii:
.LFB0:
.cfi_startproc
endbr64
movl %edi, %eax
testl %esi, %esi ; 测试第二个参数(b)
jle .L2 ; 如果b <= 0,跳转到.L2(直接返回)
xorl %edx, %edx ; i = 0
testb $1, %sil ; 测试
je .L3
addl %eax, %eax ; 如果是奇数,就会执行现在这一段
movl $1, %edx ; i = 1
cmpl $1, %esi ;
je .L11 ; 如果 b == 1,直接 return,否则会进入下面的 L3:
.p2align 4,,10
.p2align 3
.L3:
addl $2, %edx ; 每次 i += 2
sall $2, %eax ; a <<= 2
cmpl %edx, %esi ; 比较 i 和 b
jne .L3 ; 如果不相等就继续循环
.L2:
ret
.L11:
ret
.cfi_endproc
.LFE0:
.size _Z4testii, .-_Z4testii
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
- 每次迭代处理2位的移位(乘以4),而不是1位,减少循环迭代次数。
O3
.file "a.cpp"
.text
.p2align 4
.globl _Z4testii
.type _Z4testii, @function
_Z4testii:
.LFB0:
.cfi_startproc
endbr64
movl %edi, %eax
testl %esi, %esi
jle .L2
xorl %edx, %edx
testb $1, %sil
je .L3
addl %eax, %eax
movl $1, %edx
cmpl $1, %esi
je .L11
.p2align 4,,10
.p2align 3
.L3:
addl $2, %edx
sall $2, %eax
cmpl %edx, %esi
jne .L3
.L2:
ret
.L11:
ret
.cfi_endproc
.LFE0:
.size _Z4testii, .-_Z4testii
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
- 和 O2 的效果是一样的。
Fibonacci(递归)
int fib(int x) {
if (x == 0) {
return 0;
}
if (x == 1) {
return 1;
}
return fib(x - 1) + fib(x - 2);
}
利用这个代码的汇编,我们讲清楚如何栈帧操作。
O0
.file "a.cpp"
.text
.globl _Z3fibi
.type _Z3fibi, @function
_Z3fibi:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp ; 将调用者函数的基址指针 (%rbp) 的值压入栈中保存。RBP 是 callee-saved,因为接下来修改了(从调用者的栈帧底部变成当前栈帧的底部),所以必须保存。
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp ; 从现在开始,%rbp 成了一个稳定的“锚点”,指向当前栈帧的底部。函数内的局部变量和参数都将通过相对于 %rbp 的偏移来访问(例如 -20(%rbp))
.cfi_def_cfa_register 6
pushq %rbx ; 根据调用约定,%rbx 是被调用者保存的寄存器(Callee-saved)。这意味着如果函数要使用它,必须在函数开头保存其原始值,并在函数返回前恢复它。这里是因为后面的递归调用需要用到
subq $24, %rsp ; 在栈上分配24字节空间
.cfi_offset 3, -24
movl %edi, -20(%rbp) ; 将 n 存入栈帧(位置:%rbp-20)
cmpl $0, -20(%rbp)
jne .L2 ; 如果 n != 0,跳转到.L2
movl $0, %eax ; 否则返回0
jmp .L3
.L2:
cmpl $1, -20(%rbp)
jne .L4 ; 如果 n != 1,跳转到.L4(递归部分)
movl $1, %eax ; 否则返回 1
jmp .L3
.L4:
movl -20(%rbp), %eax
subl $1, %eax ; 计算 n-1,保存至 EAX
movl %eax, %edi ; 将 n-1 作为参数
call _Z3fibi ; f(n-1)
movl %eax, %ebx ; 把 f(n-1) 的结果存到 EBX 上
movl -20(%rbp), %eax
subl $2, %eax ; 计算 n-2
movl %eax, %edi ; 把 n-2 作为参数
call _Z3fibi ; f(n-2)
addl %ebx, %eax ; 把 f(n-1) + f(n-2) 作为返回值
.L3:
movq -8(%rbp), %rbx ; 恢复保存的 %rbx
leave ; 恢复栈帧(等价于 movq %rbp, %rsp + popq %rbp)
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z3fibi, .-_Z3fibi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
调用约定的规则:调用者保存 vs. 被调用者保存
x86-64架构下的寄存器被分为两大阵营,这是理解一切的关键:
-
调用者保存寄存器(Caller-saved / Volatile Registers):
- 包括:
RAX,RCX,RDX,RDI,RSI,R8,R9,R10,R11等。 - 规则: 如果一个函数(调用者)希望在这些寄存器中的值在子函数调用后仍然有效,它必须自己在调用
call指令之前手动把它们保存到栈上。子函数(被调用者)可以随意修改这些寄存器而无需恢复。 - 设计目的: 用于传递临时性的参数和结果。
- 包括:
-
被调用者保存寄存器(Callee-saved / Non-volatile Registers):
- 包括:
RBX,RBP,R12,R13,R14,R15。 - 规则: 如果一个函数(被调用者)想要使用这些寄存器,它必须在函数的开头(Prologue)保存它们的原始值,并在函数返回前(Epilogue)准确地恢复它们。对于调用者来说,它可以放心地认为这些寄存器的值在子函数调用后不会改变。
- 设计目的: 保存需要跨越函数调用的长期变量。
- 包括:
总结
| 寄存器类型 | 是否可用 | 后果 |
|---|---|---|
调用者保存 (如 RCX, RDX) |
不可用 | 值会在后续 call 中被破坏,导致错误。必须手动保存,增加指令,降低效率。 |
被调用者保存 (如 RBX, R12) |
可用且推荐 | 值在后续 call 中保持不变。只需在函数开头和结尾保存/恢复一次,安全且高效。 |
结论:
编译器不是必须使用 RBX,但它必须使用一个被调用者保存寄存器。RBX 是其中一个典型且传统的选择。它之所以出现在你的代码中,是因为它是满足“将一个值安全地跨越函数调用保存下来”这一需求的最直接、最正确、最高效的方式。
O1
.file "a.cpp"
.text
.globl _Z3fibi
.type _Z3fibi, @function
_Z3fibi:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
pushq %rbx
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
subq $8, %rsp ; 只分配了 8 字节
.cfi_def_cfa_offset 32
movl %edi, %ebx ; 把参数 n 保存到 EBX
testl %edi, %edi
je .L2 ; 如果是 0 就到 .L2
cmpl $1, %edi
je .L2 ; 如果等于1,跳转到.L2
leal -1(%rdi), %edi ; 用 leal 来计算 n - 1
call _Z3fibi ; f(n-1)
movl %eax, %ebp ; 暂存结果
leal -2(%rbx), %edi
call _Z3fibi ; f(n-2)
leal 0(%rbp,%rax), %ebx ; f(n-1)+f(n-2) 存到 EBX
.L2:
movl %ebx, %eax ; 返回值写入
addq $8, %rsp ; 恢复栈指针(分配了8字节,现在收回)
.cfi_def_cfa_offset 24
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size _Z3fibi, .-_Z3fibi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
优化带来的改进
- 减少栈使用:只分配 8 字节栈空间,而不是 24 字节
- 寄存器利用:更好地利用寄存器保存中间值,减少内存访问
- 指令选择:使用更高效的指令做算术运算(如 leal 和 testl)
- 代码共享:所有返回路径共享同一段代码
- 减少内存访问:直接在寄存器间传递值,而不是通过栈
O2
.file "a.cpp"
.text
.p2align 4
.globl _Z3fibi
.type _Z3fibi, @function
_Z3fibi:
.LFB0:
.cfi_startproc
endbr64
pushq %r15
.cfi_def_cfa_offset 16
.cfi_offset 15, -16
pushq %r14
.cfi_def_cfa_offset 24
.cfi_offset 14, -24
pushq %r13
.cfi_def_cfa_offset 32
.cfi_offset 13, -32
pushq %r12
.cfi_def_cfa_offset 40
.cfi_offset 12, -40
movl %edi, %r12d
pushq %rbp
.cfi_def_cfa_offset 48
.cfi_offset 6, -48
pushq %rbx
.cfi_def_cfa_offset 56
.cfi_offset 3, -56
subq $88, %rsp
.cfi_def_cfa_offset 144
testl %edi, %edi
je .L2
cmpl $1, %edi
je .L2
leal -1(%rdi), %r15d
xorl %r12d, %r12d
.L27:
cmpl $1, %r15d
je .L52
leal -1(%r15), %r13d
xorl %r14d, %r14d
movl %r12d, 28(%rsp)
movl %r13d, 32(%rsp)
movl %r13d, %ebp
movl %r14d, %r12d
.L26:
cmpl $1, %ebp
je .L51
movl %r15d, 36(%rsp)
leal -1(%rbp), %ecx
xorl %r14d, %r14d
movl %ebp, 40(%rsp)
movl %ecx, %ebp
.L25:
cmpl $1, %ebp
je .L50
movl %r14d, 48(%rsp)
leal -1(%rbp), %edi
xorl %r15d, %r15d
movl %ebp, %r13d
movl %ecx, 52(%rsp)
movl %r12d, 44(%rsp)
movl %edi, %r12d
.L24:
cmpl $1, %r12d
je .L49
leal -1(%r12), %r11d
xorl %r14d, %r14d
movl %r15d, 56(%rsp)
movl %r14d, 16(%rsp)
movl %r11d, %ebp
movl %edi, 60(%rsp)
movl %r11d, 64(%rsp)
movl %r12d, 68(%rsp)
.L23:
cmpl $1, %ebp
je .L48
leal -1(%rbp), %r12d
movl %ebp, 76(%rsp)
xorl %r15d, %r15d
movl %r12d, 72(%rsp)
movl %r12d, %r14d
.L22:
cmpl $1, %r14d
je .L47
leal -1(%r14), %ebp
movl %r13d, 4(%rsp)
xorl %ecx, %ecx
movl %ebp, 8(%rsp)
movl %ebp, %ebx
movl %r14d, 12(%rsp)
.L21:
cmpl $1, %ebx
je .L46
leal -1(%rbx), %ebp
xorl %r13d, %r13d
movl %ebp, %r14d
movl %ebp, %edx
movl %ecx, %ebp
movl %ebx, %ecx
movl %r14d, %ebx
.L20:
movl %ebx, %r14d
cmpl $1, %ebx
je .L45
movl %ebx, 20(%rsp)
xorl %r12d, %r12d
movl %edx, %ebx
.L16:
leal -1(%r14), %edi
movl %ecx, 24(%rsp)
call _Z3fibi
movl 24(%rsp), %ecx
addl %eax, %r12d
subl $2, %r14d
je .L54
cmpl $1, %r14d
jne .L16
movl %ebx, %edx
movl 20(%rsp), %ebx
addl $1, %r12d
.L18:
addl %r12d, %r13d
subl $2, %ebx
jne .L20
.L45:
movl %ecx, %ebx
leal 1(%r13), %esi
movl %ebp, %ecx
addl %esi, %ecx
subl $2, %ebx
cmpl $1, %edx
jne .L21
.L46:
movl 12(%rsp), %r14d
movl %ecx, %ebx
movl 8(%rsp), %ebp
addl $1, %ebx
movl 4(%rsp), %r13d
addl %ebx, %r15d
subl $2, %r14d
cmpl $1, %ebp
jne .L22
.L47:
movl 76(%rsp), %ebp
movl 72(%rsp), %r12d
addl $1, %r15d
addl %r15d, 16(%rsp)
subl $2, %ebp
cmpl $1, %r12d
jne .L23
.L48:
movl 16(%rsp), %r14d
movl 56(%rsp), %r15d
movl 68(%rsp), %r12d
movl 64(%rsp), %r11d
addl $1, %r14d
movl 60(%rsp), %edi
addl %r14d, %r15d
subl $2, %r12d
cmpl $1, %r11d
jne .L24
.L49:
movl 48(%rsp), %r14d
movl %r13d, %ebp
addl $1, %r15d
movl 52(%rsp), %ecx
movl 44(%rsp), %r12d
subl $2, %ebp
addl %r15d, %r14d
cmpl $1, %edi
jne .L25
.L50:
movl 40(%rsp), %ebp
addl $1, %r14d
movl 36(%rsp), %r15d
addl %r14d, %r12d
subl $2, %ebp
cmpl $1, %ecx
jne .L26
.L51:
movl %r12d, %r14d
movl 32(%rsp), %r13d
movl 28(%rsp), %r12d
subl $2, %r15d
addl $1, %r14d
addl %r14d, %r12d
cmpl $1, %r13d
jne .L27
.L52:
addl $1, %r12d
.L2:
addq $88, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 56
movl %r12d, %eax
popq %rbx
.cfi_def_cfa_offset 48
popq %rbp
.cfi_def_cfa_offset 40
popq %r12
.cfi_def_cfa_offset 32
popq %r13
.cfi_def_cfa_offset 24
popq %r14
.cfi_def_cfa_offset 16
popq %r15
.cfi_def_cfa_offset 8
ret
.p2align 4,,10
.p2align 3
.L54:
.cfi_restore_state
movl %ebx, %edx
movl 20(%rsp), %ebx
jmp .L18
.cfi_endproc
.LFE0:
.size _Z3fibi, .-_Z3fibi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
性能权衡
这种优化带来了性能提升,但代价是代码大小增加:
优点:
- 减少函数调用开销
- 更好的指令级并行性
- 更高效的内存访问模式
缺点:
- 代码大小显著增加
- 可读性降低
- 可能增加指令缓存压力
O3
和 O2 一样,略。
尾递归优化
int foo(int x) {
if (x <= 0) {
return 0;
}
return foo(x - 1) + 1;
}
O1
我们这次先看 O1 的结果,因为他比较符合我们对源代码(C++)的认知。
.file "a.cpp"
.text
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
movl $0, %eax ; 预先将返回值设为0
testl %edi, %edi ; 测试参数n是否为0或负数
jle .L5 ; 如果n <= 0,直接返回0
subq $8, %rsp ; 分配8字节栈空间(用于对齐)
.cfi_def_cfa_offset 16
subl $1, %edi ; 参数减1
call _Z3fooi ; foo(x-1)
addl $1, %eax ; 结果加1
addq $8, %rsp ; 恢复栈指针
.cfi_def_cfa_offset 8
ret
.L5:
ret
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
O0
.file "a.cpp"
.text
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp
movl %edi, -4(%rbp)
cmpl $0, -4(%rbp)
jg .L2
movl $0, %eax
jmp .L3
.L2:
movl -4(%rbp), %eax
subl $1, %eax
movl %eax, %edi
call _Z3fooi
addl $1, %eax
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
O2
.file "a.cpp"
.text
.p2align 4
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
xorl %eax, %eax
testl %edi, %edi
cmovns %edi, %eax ; 如果 n 是非负数(non-negative),将 n 移动到 eax
ret
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
编译器完全识别并消除了递归调用
尾递归优化其二
int foo(int x) {
if (x <= 0) {
return 0;
}
return foo(x - 1) + (x & (x - 1));
}
O0
.file "a.cpp"
.text
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
subq $16, %rsp ; 开 16 个字节的栈空间
movl %edi, -4(%rbp) ; 保存 x
cmpl $0, -4(%rbp)
jg .L2 ; 大于零进递归
movl $0, %eax
jmp .L3 ; 否则返回 0
.L2:
movl -4(%rbp), %eax ;
subl $1, %eax ; 得到 x-1
movl %eax, %edi ; foo(x-1)
call _Z3fooi
movl -4(%rbp), %edx ;
subl $1, %edx ; 得到 x-1
andl -4(%rbp), %edx ; 得到 x & (x-1)
addl %edx, %eax ; foo(x-1) + (x & (x-1))
.L3:
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
O1
.file "a.cpp"
.text
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
movl $0, %eax
testl %edi, %edi
jle .L5
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
pushq %rbx ; 保存 RBX 寄存器
.cfi_def_cfa_offset 24
.cfi_offset 3, -24
subq $8, %rsp ; 开 8 个字节的栈空间
.cfi_def_cfa_offset 32
movl %edi, %ebp ; 保存 x
leal -1(%rdi), %ebx
movl %ebx, %edi ; 计算 x-1 为参数
call _Z3fooi ; foo(x-1)
andl %ebp, %ebx ; x & (x-1)
addl %ebx, %eax ; EAX = foo(x-1) + (x & (x-1))
addq $8, %rsp ; 恢复栈空间
.cfi_def_cfa_offset 24
popq %rbx
.cfi_def_cfa_offset 16
popq %rbp
.cfi_def_cfa_offset 8
ret
.L5:
.cfi_restore 3
.cfi_restore 6
ret
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
O2
.file "a.cpp"
.text
.p2align 4
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
xorl %edx, %edx ; EDX = 0 作为累加器
testl %edi, %edi
jle .L1 ; 如果 n <= 0 就跳转到结束区域
.p2align 4,,10
.p2align 3
.L2:
movl %edi, %eax ; EAX = n
subl $1, %edi ; EDI = n - 1
andl %edi, %eax ; EAX = n & (n - 1)
addl %eax, %edx ; 累加
testl %edi, %edi
jne .L2 ; 如果 n != 0 就继续循环
.L1:
movl %edx, %eax
ret
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:
O2 优化将递归完全转换为迭代,消除所有函数调用开销。
O3
这个 O3 优化版本展示了编译器如何通过向量化(SIMD) 和循环展开来进一步优化代码。我们不讲解具体的实现,仅仅介绍大体的框架。
- 向量化主循环:使用 SSE 指令并行处理 4 个元素
- 标量尾端处理:处理不能被 4 整除的剩余元素
- 循环展开:进一步优化标量部分
.file "a.cpp"
.text
.p2align 4
.globl _Z3fooi
.type _Z3fooi, @function
_Z3fooi:
.LFB0:
.cfi_startproc
endbr64
testl %edi, %edi
jle .L19
cmpl $4, %edi
jle .L8
movd %edi, %xmm6
movl %edi, %edx
movdqa .LC1(%rip), %xmm5
xorl %eax, %eax
pshufd $0, %xmm6, %xmm2
shrl $2, %edx
pxor %xmm0, %xmm0
pcmpeqd %xmm4, %xmm4
paddd .LC0(%rip), %xmm2
.p2align 4,,10
.p2align 3
.L5:
movdqa %xmm2, %xmm3
addl $1, %eax
paddd %xmm5, %xmm2
movdqa %xmm3, %xmm1
paddd %xmm4, %xmm1
pand %xmm3, %xmm1
paddd %xmm1, %xmm0
cmpl %eax, %edx
jne .L5
movdqa %xmm0, %xmm1
movl %edi, %edx
psrldq $8, %xmm1
andl $-4, %edx
paddd %xmm1, %xmm0
movdqa %xmm0, %xmm1
psrldq $4, %xmm1
paddd %xmm1, %xmm0
movd %xmm0, %eax
testb $3, %dil
je .L1
subl %edx, %edi
.L3:
leal -1(%rdi), %edx
movl %edx, %ecx
andl %edi, %ecx
addl %ecx, %eax
testl %edx, %edx
je .L1
leal -2(%rdi), %ecx
andl %ecx, %edx
addl %edx, %eax
testl %ecx, %ecx
je .L1
leal -3(%rdi), %edx
andl %edx, %ecx
addl %ecx, %eax
testl %edx, %edx
je .L1
subl $4, %edi
andl %edx, %edi
addl %edi, %eax
.L1:
ret
.p2align 4,,10
.p2align 3
.L19:
xorl %eax, %eax
ret
.L8:
xorl %eax, %eax
jmp .L3
.cfi_endproc
.LFE0:
.size _Z3fooi, .-_Z3fooi
.section .rodata.cst16,"aM",@progbits,16
.align 16
.LC0:
.long 0
.long -1
.long -2
.long -3
.align 16
.LC1:
.long -4
.long -4
.long -4
.long -4
.ident "GCC: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0"
.section .note.GNU-stack,"",@progbits
.section .note.gnu.property,"a"
.align 8
.long 1f - 0f
.long 4f - 1f
.long 5
0:
.string "GNU"
1:
.align 8
.long 0xc0000002
.long 3f - 2f
2:
.long 0x3
3:
.align 8
4:

浙公网安备 33010602011771号