[译]优化汇编例程(4)

栏目: 编程语言 · 发布时间: 5年前

内容简介:4. ABI标准ABI表示应用程序二进制接口(Application Binary Interface)。ABI是函数如何调用,参数与返回值如何传递,允许函数改变哪些寄存器的标准。在合并汇编与高级语言时,遵守适合的ABI标准是重要的。调用惯例等的细节涵盖在手册5《不同C++编译器与操作系统的调用惯例》中。这里,为了您的方便。汇总了最重要的规则。4.1. 寄存器的使用

4. ABI标准

ABI表示应用程序二进制接口(Application Binary Interface)。ABI是函数如何调用,参数与返回值如何传递,允许函数改变哪些寄存器的标准。在合并汇编与高级语言时,遵守适合的ABI标准是重要的。调用惯例等的细节涵盖在手册5《不同C++编译器与操作系统的调用惯例》中。这里,为了您的方便。汇总了最重要的规则。

4.1. 寄存器的使用

16 DOS Windows

32 Windows Unix

64 Windows

64 Unix

可以自由使用的寄存器

AX , BX , CX , DX , ES , ST(0) - ST(7)

EAX , ECX , EDX , ST(0) - ST(7) , XMM0-XMM7,

YMM0-YMM7

RAX , RCX , RDX , R8 - R11 , ST(0) - ST(7) , XMM0-XMM5, YMM0-YMM5, YMM6H-YMM15H

RAX , RCX , RDX , RSI , RDI , R8 - R11 , ST(0)-ST(7) ,

XMM0-XMM15,

YMM0-YMM15

必须保存及恢复的寄存器

SI, DI, BP, DS

EBX, ESI, EDI, EBP

RBX, RSI, RDI, RBP, R12-R15, XMM6-XMM15

RBX, RBP, R12-R15

不能改变的寄存器

DS, ES, FS, GS, SS

用于参数传递的寄存器

(ECX)

RCX, RDX, R8,R9, XMM0-XMM3, YMM0-YMM3

RDI, RSI, RDX, RCX, R8, R9, XMM0-XMM7, YMM0-YMM7

用于返回值的寄存器

AX, DX, ST(0)

EAX, EDX, ST(0)

RAX, XMM0, YMM0

RAX , RDX , XMM0 , XMM1 , YMM0 ST(0) , ST(1)

表14.1. 寄存器的使用

在进行任何调用或返回前,浮点寄存器ST(0) ~ ST(7)必须是空的,除了用于函数返回值。在任何调用或返回前,必须通过EMMS清零MMX寄存器。在任何调用或返回到非VEX代码前,必须通过VZEROUPPER清零YMM寄存器。

算术标记可以自由改变。在32位与64位系统中,方向标记可以临时设置,但在任何调用或返回前,必须清除。在受保护的操作系统中,中断标记不能被清除。浮点控制字与MXCR寄存器的比特6 ~ 15,在修改它们的函数中必须被保存与恢复。

寄存器FS与GS用于线程环境块等,不应该被改变。其他段寄存器,除了在分段16位模型里,也不应该被改变。

    1. 数据存储

在C或C++中的一个函数里声明的变量与对象,保存在栈上,并相对于栈指针或栈框取址。出于两个原因,这是保存数据最高效的方式。首先,在该函数返回时,用于局部储存的栈空间被释放,可被下一个调用的函数重用。反复使用相同的内存区域改进了数据缓存。第二个原因是,保存在栈上的数据,通常可以通过相对于一个指针的8比特偏移来访问,而不是数据段中访问数据要求的32位。这使得代码更紧凑,因此需要更少的代码缓存或追踪缓存空间。

C++中的全局与静态数据保存在数据段中,在32位系统中以32位绝对地址,在64位系统中以32位RIP相对地址来访问。在C++中保存数据的第三种方式是使用new或malloc分配空间。如果速度是关键,应该避免这个方法。

4.2. 函数调用惯例

16 位模式DOS 与Windows 3.x 中的调用惯例

函数参数在栈上传递,第一个参数在最低地址处。

这对应首先压入最后的参数。栈由调用者清理。8或16比特大小的参数使用一个字的栈空间。超过16比特的参数以little-endian的形式保存,即最低位的字在最低地址。所有栈参数对齐到2。

在大多数情形里,函数返回值在寄存器中传递。8位整数在AL中返回,16位整数与近指针在AX,32位整数与远指针在DX:AX,布尔在AX,浮点值在ST(0)。

32 位Windows ,Linux ,BSD ,Mac OS X 中的调用惯例

根据以下调用惯例,函数参数在栈上传递:

调用惯例

栈上的参数序

参数的删除者

__cdecl

第一个参数在最低地址

调用者

__stdcall

第一个参数在最低地址

例程

__fastcall Microsoft与Gnu

前两个参数在ecx,edx。余下同__stdcall

例程

__fastcall Borland

前三个参数在eax,edx,ecx。余下同__stdcall

例程

_pascal

第一个参数在最高地址

例程

__thiscall Microsoft

This在ecx。余下同__stdcall

例程

表4.2. 32位模式中的调用惯例

Linux 中,__cdecl调用惯例是默认的。在Windows中,__cdecl调用惯例也是缺省的,除了对成员函数,系统函数与DLL函数。在.obj与.lib文件中静态链接的模块最好使用__cdecl,而在.dll文件里的动态链接库应该使用__stdcall。对Windows下的成员函数,Microsoft、Intel、Digital Mars与Codeplay编译器缺省使用__thiscall,Borland编译器使用__cdecl,this作为第一个参数。

对具有整数参数的函数,最快的调用惯例是__fastcall,但这个调用惯例不是标准的。

记住在栈上压入一个值时,栈指针是递减的。这意味着第一个压入的参数在最高地址,符合_pascal惯例。你必须反序压入参数以满足__cdecl与__stdcall惯例。

32位或更小的参数使用4字节栈空间。超过32位的参数以little-endian的形式保存,即最低位的字在最低地址,并对齐到4。

Mac OS X与Gnu编译器3及更新版本在每条调用指令前对齐栈到16,尽管这个行为不一致。声明其他对齐是可能的,这会导致不兼容。参考手册5《不同C++编译器与操作系统的调用惯例》。

在大多数情形里,函数返回值在寄存器中传递。8位整数在AL中返回,16位整数在AX,32位整数、指针、引用与布尔在EAX,64位整数在EDX:EAX,浮点值在ST(0)。

关于复合类型(struct、class、union)及向量类型(__m64、__m128、__m256)参数的细节参考手册5《不同C++编译器与操作系统的调用惯例》。

64 位Windows 中的调用惯例

如果是整数,第一个参数在RCX中传递,如果是float或double,在XMM0中。第二个参数在RDX或XMM1中传递。第三个参数在R8或XMM2中传递。第四个参数在R9或XMM3中传递。注意,如果使用了XMM0,RCX就不用于参数传递。不超过4个参数可以在寄存器中传递,无论类型。更多的参数在栈上传递,第一个参数在最低地址处且对齐到8。成员函数有作为第一个参数的this。

除了在栈上传递的参数,调用者还必须在栈上分配32字节的空闲空间。这是一个影子空间(shadow space),如果需要,被调用函数可以在其中保存四个参数寄存器。影子空间是保存前四个参数的地方,如果它们根据__cdecl规则在栈上传递。影子空间属于被调用函数,它被允许在影子空间里保存参数(或别的任何东西)。调用者必须保留32字节的影子空间,即使函数没有参数。调用者必须清理栈,包括影子空间。返回值在RAX或XMM0中。

在任何CALL指令前,栈指针必须对齐到16,因此RSP的值,在函数入口,是8模16。在将XMM寄存器保存到栈时,函数可以依赖这个对齐。

关于复合类型(struct、class、union)及向量类型(__m64、__m128、__m256)参数的细节参考手册5《不同C++编译器与操作系统的调用惯例》。

64 位Linux 、BDS 与Mac OS X 中的调用惯例

前六个参数分别在RDI,RSI,RDX,RCX,R8,R9中传递。前八个浮点参数在XMM0 ~ XMM7中传递。所有这些寄存器都可以使用,因此最多14个参数可以在寄存器中传递。更多的参数在栈上传递,第一个参数在最低地址处且对齐到8。如果有任何参数在栈上,栈由调用者清理。没有影子空间。成员函数有作为第一个参数的this。返回值在RAX或XMM0中。

在任何CALL指令前,栈指针必须对齐到16,因此RSP的值,在函数入口,是8模16。在将XMM寄存器保存到栈时,函数可以依赖这个对齐。

范围在[RSP-1]到[RSP-128]的地址称为红区。函数可以安全地将数据保存到红区,只要它不会被任何PUSH或CALL指令改写。

关于复合类型(struct、class、union)及向量类型(__m64、__m128、__m256)参数的细节参考手册5《不同C++编译器与操作系统的调用惯例》。

4.3. 名字重整(mangling)与名字修饰

C++中对函数重载的支持,使向链接器提供关于函数参数的信息成为必须。这通过对函数名添加参数类型代码来完成。这称为名字重整。名字重整代码传统上是编译器特定的。幸好,一个不断增长的趋势是标准化这个区域,以改进不同编译器间的兼容性。不同编译器的名字重整代码的细节在手册5《不同C++编译器与操作系统的调用惯例》中描述。

不兼容的名字重整代码问题,通过使用extern “C”声明,最容易解决。使用extern “C”声明的函数没有名字重整。仅有的修饰是16与32位Windows以及32与64位Mac OS中的一个下划线。对带有__stdcall与__fastcall声明的函数名,有某个额外的修饰。

extern “C”声明不能用于成员函数、重载函数、操作符及其他 C语言 不正常的构造。在这些情形中,你可以通过定义一个调用非重整函数的重整函数,来避免名字重整。如果重整函数被声明为内联,那么编译器将把对重整函数的调用替换为对非重整函数的调用。例如,在汇编中不使用名字重整定义一个重载C++操作符:

class C1; // unmangled assembly function;

extern "C" C1 cplus (C1 const & a, C1 const & b);

// mangled C++ operator

inline C1 operator + (C1 const & a, C1 const & b) {

// operator + replaced inline by function cplus

return cplus(a, b);

}

重载函数可以相同的方式内联。类成员函数可以被翻译为友元函数,如第42页指令7.1b所示。

4.5. 函数例子

以下例子展示了如何以汇编编写遵循调用惯例的函数。首先是C++代码:

// Example 4.1a

extern "C" double sinxpnx (double x, int n) {

return sin(x) + n * x;

}

相同的函数可以汇编编写。下面的例子展示了对不同平台编写的相同函数。

; Example 4.1b. 16-bit DOS and Windows 3.x

ALIGN 4

_sinxpnx PROC NEAR

; parameter x = [SP+2]

; parameter n = [SP+10]

; return value = ST(0)

push bp                                  ; bp must be saved

mov bp, sp                             ; stack frame

fild word ptr [bp+12]           ; n

fld qword ptr [bp+4]            ; x

fmul st(1), st(0)                     ; n*x

fsin                                          ; sin(x)

fadd                                        ; sin(x) + n*x

pop bp                                    ; restore bp

ret                                           ; return value is in st(0)

_sinxpnx ENDP

在16位模式中,我们需要BP作为栈框,因为SP不能用作基址指针。整数n只是16位。对sin函数我使用了硬件指令FSIN。

; Example 4.1c. 32-bit Windows

EXTRN _sin:near

ALIGN 4

_sinxpnx PROC near

; parameter x = [ESP+4]

; parameter n = [ESP+12]

; return value = ST(0)

fld qword ptr [esp+4]             ; x

sub esp, 8                                 ; make space for parameter x

fstp qword ptr [esp]               ; store parameter for sin; clear st(0)

call _sin                                     ; library function for sin()

add esp, 8                                 ; clean up stack after call

fild dword ptr [esp+12]          ; n

fmul qword ptr [esp+4]          ; n*x

fadd                                            ; sin(x) + n*x

ret                                               ; return value is in st(0)

_sinxpnx ENDP

这里,我选择使用库函数__sin代替FSIN。在某些情形里,这可能会更快,因为FSIN提供了比所需更高的精度。_sin的参数在栈上作为8字节传递。

; Example 4.1d. 32-bit Linux

EXTRN sin:near

ALIGN 4

sinxpnx PROC near

; parameter x = [ESP+4]

; parameter n = [ESP+12]

; return value = ST(0)

fld qword ptr [esp+4]                ; x

sub esp, 12                                  ; Keep stack aligned by 16 before call

fstp qword ptr [esp]                   ; Store parameter for sin; clear st(0)

call sin                                           ; Library proc. may be faster than fsin

add esp, 12                                   ; Clean up stack after call

fild dword ptr [esp+12]              ; n

fmul qword ptr [esp+4]              ; n*x

fadd                                                ; sin(x) + n*x

ret                                                   ; Return value is in st(0)

sinxpnx ENDP

在32位Linux中,函数名上没有下划线。在Linux中,栈必须保存16字节对齐(GCC 3或更新版本)。对sinxpnx的调用从ESP减去4。我们从ESP再减去12,使减去总数为16。我们可以减去更多,只要总数是16的倍数。在例子4.1c中,我们仅从ESP减去8,因为在32位Windows中栈仅对齐到4。

; Example 4.1e. 64-bit Windows

EXTRN sin:near

ALIGN 4

sinxpnx PROC

; parameter x = xmm0

; parameter n = edx

; return value = xmm0

push rbx                                          ; rbx must be saved

movaps [rsp+16], xmm6              ; save xmm6 in my shadow space

sub rsp, 32                                      ; shadow space for call to sin

mov ebx, edx                                  ; save n

movsd xmm6, xmm0                    ; save x

call sin                                              ; xmm0 = sin(xmm0)

cvtsi2sd xmm1, ebx                       ; convert n to double

mulsd xmm1, xmm6                      ; n * x

addsd xmm0, xmm1                      ; sin(x) + n * x

add rsp, 32                                       ; restore stack pointer

movaps xmm6, [rsp+16]                ; restore xmm6

pop rbx                                              ; restore rbx

ret                                                       ; return value is in xmm0

sinxpnx ENDP

在64位Windows中,函数参数在寄存器中传递。ECX没有用于参数传递,因为第一个参数不是整数。我们使用RBX与XMM6在sin的调用过程中保存n与x。对此,我们必须使用具有被调用者保存状态的寄存器,且我们在使用它们之前,必须在栈上保存这些寄存器。在调用sin前,栈必须对齐到16,我们可以依赖在调用sinxpnx前栈必须对齐到16这个条件。对sinxpnx的调用从RSP减去8;PUSH RBX指令减去8;SUB指令减去32。减去的总数是8+8+32 = 48,它是16的倍数,因此保证了正确的对齐。栈上额外的32字节是用于sin调用的影子空间。注意例子4.1e不包括对异常处理的支持。如果程序依赖于捕捉在函数sin里产生的异常,添加带有栈回滚信息的表是必要的。

; Example 4.1f. 64-bit Linux

EXTRN sin:near

ALIGN 4

sinxpnx PROC

PUBLIC sinxpnx

; parameter x = xmm0

; parameter n = edi

; return value = xmm0

push rbx                                               ; rbx must be saved

sub rsp, 16                                           ; make local space and align stack by 16

movaps [rsp], xmm0                          ; save x

mov ebx, edi                                        ; save n

call sin                                                   ; xmm0 = sin(xmm0)

cvtsi2sd xmm1, ebx                            ; convert n to double

mulsd xmm1, [rsp]                              ; n * x

addsd xmm0, xmm1                           ; sin(x) + n * x

add rsp, 16                                           ; restore stack pointer

pop rbx                                                  ; restore rbx

ret                                                          ; return value is in xmm0

sinxpnx ENDP

64位Linux不使用与64位Windows一样的寄存器进行参数传递。没有具有被调用者保存状态的XMM寄存器,因此在sin调用期间,我们必须把x保存在栈上,尽管把它保存在寄存器可能更快(把x保存在一个64位整数寄存器是可能的,但慢)。n仍然可以保存在一个具有被调用者保存状态的通用寄存器中。栈对齐到16。栈上不需要影子空间。红区不能使用,因为它将被对sin的调用改写。注意例子4.1f不包括对异常处理的支持。如果程序依赖于捕捉在函数sin里产生的异常,添加带有栈回滚信息的表是必要的。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Art of Computer Programming, Volume 2

The Art of Computer Programming, Volume 2

Knuth, Donald E. / Addison-Wesley Professional / 1997-11-04 / USD 79.99

Finally, after a wait of more than thirty-five years, the first part of Volume 4 is at last ready for publication. Check out the boxed set that brings together Volumes 1 - 4A in one elegant case, and ......一起来看看 《The Art of Computer Programming, Volume 2》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具