第78部分- Linux x86 64位汇编 创建优化的代码

第78部分- Linux x86 64位汇编 创建优化的代码

我们可以用gcc编译器从C程序创建编译器优化后的汇编代码,然后分析优化。

以tempconv.c文件为例,将华氏温度转为摄氏温度。

#include <stdio.h>

float convert(int deg)
{
        float result;
        result = (deg - 32.) / 1.8;
        return result;
}

int main()
{
        int i = 0;
        float result;
        printf("    Temperature Conversion Chart\n");
        printf("Fahrenheit       Celsius\n");
        for(i = 0; i < 230; i = i + 10)
        {
                result = convert(i);
                printf("  %d             %5.2f\n", i, result);
        }
        return 0;
}

汇编之后得到汇编代码:

gcc -S tempconv.c

编译器产生汇编代码

汇编代码如下,我们将其进行了注释。:

	.file	"tempconv.c";//源文件名字
	.text                    ;//定义代码段,是只读和可执行的,后面那些指令都属于.text段。
	.globl	convert        ;//定义函数符号,.globl指示告诉汇编器,这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号
	.type	convert, @function  ;//定义convert函数
convert:
.LFB0:
	.cfi_startproc ;//函数开始符号
	pushq	%rbp;//压栈rbp保存
	.cfi_def_cfa_offset 16;// CFA(Canonical Frame Address),CFA定义为调用站点上的前一帧的堆栈指针的值, CFA现在与当前堆栈指针的偏移量为16个字节。CFI是CFI代表调用帧信息.
	.cfi_offset 6, -16;//CFI指令用于调试。 它允许调试器展开堆栈。寄存器的先前值保存在与CFA偏移的位置。register 6 
	movq	%rsp, %rbp;//复制当前rsp到rbp中
	.cfi_def_cfa_register 6;// .cfi_def_cfa_register修改用于计算CFA的规则。将使用寄存器而不是旧的寄存器。 偏移量保持不变。register 6 
	movl	%edi, -20(%rbp) ;//复制edi到rbp中,在堆栈下方空地处,这里就是函数参数
	cvtsi2sd	-20(%rbp), %xmm0;//将参数,从 1个双字有符号整数变成1个双精度浮点数,到xmm0中。
	movsd	.LC0(%rip), %xmm1;//将双字节32传送到xmm1中。
	subsd	%xmm1, %xmm0
	movsd	.LC1(%rip), %xmm1;//将双字节1.8传送到xmm1中。
	divsd	%xmm1, %xmm0;//除以1.8
	cvtsd2ss	%xmm0, %xmm2;// 将双精度转换为单精度浮点值

	movss	%xmm2, -4(%rbp);//结果移动到堆栈
	movss	-4(%rbp), %xmm0;//通过xmm0返回
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret       ;//函数返回
	.cfi_endproc  ;//函数结束符号
.LFE0:
	.size	convert, .-convert;//函数字节数量
	.section	.rodata;//只读段
	.align 8 ;// .align的作用在于对指令或者数据的存放地址进行对齐,有些CPU架构要求固定的指令长度并且存放地址相对于2的幂指数圆整,否则程序无法正常运行,如arm。.align的作用范围只限于紧跟它的那条指令或者数据,而接下来的指令或者数据的地址由上一条指令的地址和其长度决定。
.LC2:
	.string	"    Temperature Conversion Chart" ;//定义字符串
.LC3:
	.string	"Fahrenheit       Celsius"  ;//定义字符串
.LC4:
	.string	"  %d             %5.2f\n"
	.text                         ;//定义代码段,只读并可执行
	.globl	main
	.type	main, @function   ;//定义main函数
main:                            
.LFB1:
	.cfi_startproc;//函数开始符号
	pushq	%rbp;//压栈rbp保存
	.cfi_def_cfa_offset 16;// CFA现在与当前堆栈指针的偏移量为16个字节
	.cfi_offset 6, -16;//6号寄存器是rbp
	movq	%rsp, %rbp;//复制当前rsp到rbp中
	.cfi_def_cfa_register 6
	subq	$16, %rsp;//腾出堆栈空地,2格。
	movl	$0, -8(%rbp) ;//复制本地变量0到堆栈中。
	leaq	.LC2(%rip), %rdi;//字符串地址赋值给rdi,调用输出函数puts,源代码中的printf
	call	puts@PLT
	leaq	.LC3(%rip), %rdi;// 字符串地址赋值给rdi,调用输出函数puts, 源代码中的printf
	call	puts@PLT
	movl	$0, -8(%rbp) ;//复制本地变量i=0到堆栈中
	jmp	.L4
.L5:
	movl	-8(%rbp), %eax;//复制本地变量i到eax
	movl	%eax, %edi;//复制本地变量i到edi
	call	convert;//调用函数convert。
	movd	%xmm0, %eax;//从xmm0获取结果
	movl	%eax, -4(%rbp)
	cvtss2sd	-4(%rbp), %xmm0;// 将单精度转换为双精度浮点值
	movl	-8(%rbp), %eax;//华氏摄氏度,取出->eax->esi,调用printf
	movl	%eax, %esi;//华氏设置度整型放于esi.
	leaq	.LC4(%rip), %rdi;//字符串地址。
	movl	$1, %eax
	call	printf@PLT;//调用printf函数
	addl	$10, -8(%rbp) ;//调用增加10给变量i,即是for中i+10
.L4:
	cmpl	$229, -8(%rbp);//对比229和-8(%rbp)的变量,就是for循环中的比较
	jle	.L5;//小于等于就调整到.L5,否则就退出程序。
	movl	$0, %eax;//移动0到eax,函数返回。
	leave;// Leave的作用相当mov esp,ebp和pop ebp。
	.cfi_def_cfa 7, 8;//7号寄存器是rsp。实现rsp+8。
	ret
	.cfi_endproc;//函数开始符号
.LFE1:
	.size	main, .-main;//该函数的函数字节数
	.section	.rodata;//只读段,不可执行
	.align 8
.LC0:;//保存32浮点。
	.long	0
	.long	1077936128
	.align 8
.LC1: ;//保存1.8浮点。
	.long	3435973837
	.long	1073532108
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0";//GCC注释,连接器会移除。
	.section	.note.GNU-stack,"",@progbits

这里的.section是汇编指示(Assembler Directive)或叫做伪操作(Pseudo-operation)

.section指示把代码划分成若干个段(Section),程序被操作系统加载执行时,每个段被加载到不同的地址,操作系统对不同的页面设置不同的读、写、执行权限。

这里的CFA描述如下:

:                :
|    whatever    | <--- CFA
+----------------+
| return address |
+----------------+
| reserved space | <--- %rsp == CFA - 16
+----------------+

使用.cfi_def_cfa_offset指令在调试信息中声明了堆栈指针的更改,并且可以看到CFA现在与当前堆栈指针的偏移量为16个字节。

优化后代码分析

注释结束后,我们看下优化点分析。

#gcc -S -O3 tempconv3.s tempconv.c

	.file	"tempconv.c"
	.text
	.p2align 4,,15
	.globl	convert
	.type	convert, @function
convert:
.LFB23:
	.cfi_startproc
	pxor	%xmm0, %xmm0;//异或清零
	cvtsi2sd	%edi, %xmm0;//是参数,华氏摄氏度。
	subsd	.LC0(%rip), %xmm0;//直接减去LC0中的38
	divsd	.LC1(%rip), %xmm0;//直接除去LC1中的1.8。
	cvtsd2ss	%xmm0, %xmm0;//转化为单精度。
	ret
	.cfi_endproc
.LFE23:
	.size	convert, .-convert
	.section	.rodata.str1.8,"aMS",@progbits,1
	.align 8
.LC2:
	.string	"    Temperature Conversion Chart"
	.section	.rodata.str1.1,"aMS",@progbits,1
.LC3:
	.string	"Fahrenheit       Celsius"
.LC4:
	.string	"  %d             %5.2f\n"
	.section	.text.startup,"ax",@progbits
	.p2align 4,,15
	.globl	main
	.type	main, @function
main:
.LFB24:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	pushq	%rbx;//保存rbx寄存器
	.cfi_def_cfa_offset 24
	.cfi_offset 3, -24
	leaq	.LC2(%rip), %rdi
	leaq	.LC4(%rip), %rbp
	xorl	%ebx, %ebx;//ebx清零
	subq	$8, %rsp
	.cfi_def_cfa_offset 32
	call	puts@PLT
	leaq	.LC3(%rip), %rdi
	call	puts@PLT
	.p2align 4,,10
	.p2align 3
.L4:
	pxor	%xmm0, %xmm0
	movl	%ebx, %edx
	movq	%rbp, %rsi
	movl	$1, %edi
	movl	$1, %eax
	cvtsi2sd	%ebx, %xmm0
	addl	$10, %ebx
	subsd	.LC0(%rip), %xmm0
	divsd	.LC1(%rip), %xmm0
	cvtsd2ss	%xmm0, %xmm0
	cvtss2sd	%xmm0, %xmm0
	call	__printf_chk@PLT
	cmpl	$230, %ebx
	jne	.L4
	addq	$8, %rsp
	.cfi_def_cfa_offset 24
	xorl	%eax, %eax
	popq	%rbx
	.cfi_def_cfa_offset 16
	popq	%rbp
	.cfi_def_cfa_offset 8
	ret
	.cfi_endproc
.LFE24:
	.size	main, .-main
	.section	.rodata.cst8,"aM",@progbits,8
	.align 8
.LC0:
	.long	0
	.long	1077936128
	.align 8
.LC1:
	.long	3435973837
	.long	1073532108
	.ident	"GCC: (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

可以通过如下直接汇编执行

#as -o tempconv3.o tempconv3.s

#gcc -o tempconv3 tempconv3.o

我们发现main中其实没有调用convert函数了,被优化掉了,功能被直接嵌入到main函数中了。

而convert函数本身也是被大量精简化了。

 

参考

这里由于细节比较多,加入了一个参考链接,关于cfi的描述。

https://sourceware.org/binutils/docs-2.17/as/CFI-directives.html#CFI-directives

CFI directives in assembly files 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值