【笔记】汇编入门

鉴于在华子呆着没事干,开始学习汇编。

简单循环

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:
posted @ 2025-09-17 17:00  Imakf  阅读(14)  评论(0)    收藏  举报