尽管ARM核在智能终端市场风光无限,但依赖于低功耗、超强的处理能力和较为便宜的License, MIPS内核系列芯片依然在电子产品和网络设备中占有很大的市场份量,咱们国家的龙芯用的也是MIPS架构。MIPS的64位系统产品主要面向服务器,32位主要用于消费类电子和网络设备等方面。32位MIPS有两种指令集模式,一种是MIPS32指令集,一种是MIPS16e指令集模式。后者指令编码是16比特,号称能够使编译后的代码减少30%左右,主要用于控制器系列产品中节省内存。
ABI是application binaryinterface,表示应用二进制编程接口,主要介绍寄存器使用约定、参数传递、栈帧结构、混合编程等内容。其目标是二进制应用程序兼容,比API要提供更高层次的兼容。这里谈谈MIPS ABI o32版本标准。
一、寄存器使用约定
1. MIPS32寄存器
寄存器名称和使用约定如下图:
寄存器29 (sp)——这是堆栈指针。它指向堆栈中的下一个可用单元。从栈顶获取一个字节是sp-1地址的内容。寄存器30 (fp)——这是帧指针。它指向当前函数的帧。在需要时,每个函数会创建一个新帧,通过该帧分配自动变量和临时变量。编译器优化可能会取消通过帧指针进行的堆栈指针引用,而将它们转换为通过堆栈指针进行的等效引用。这种优化使得帧指针可以用作通用寄存器。
2. MIPS16e寄存器
其与32位指令集模式对照如下图:
Mips16e模式下,$0是S0,$1是S1。即mips16e模式下主要用a0,a1,a2,a3,v0,v1,s0,s1,t8,sp,ra这11个寄存器。
Move指令可以访问32个寄存器,乘法指令可以用HI和LO,也有读取HI和LO的指令。t8($24)一般是用于条件测试指令
3. 函数调用时的寄存器使用约定
1)寄存器a0-a3 用于向函数传递参数。调用函数时,不会保留这些寄存器的值。
2)寄存器t0-t7 和t8-t9 是由调用程序保存的寄存器。调用函数必须将这些值压入堆栈,以保存寄存器的值。
3)寄存器s0-s7 是由被调用程序保存的寄存器。被调用函数必须保存这些寄存器中会被修改的寄存器的值。
4)如果优化器取消将寄存器s8/fp 用作帧指针,那么该寄存器的值需要保存。否则, s8/fp 是被保留的寄存器。
5)寄存器ra中包含函数调用的返回地址。
6)ra和sp在程序入口处在有必要时应该保存。
二、参数传递
1. 参数从左到右存放在a0,a1,a2,a3寄存器中,返回值存放在v0和v1寄存器中。
2. 每个参数不论是字还是字节都用一个寄存器来传,自动进行0扩展或者有符号扩展。如果是数据结构传递需要模拟内存的存放,而不是一个字节也用一个寄存器来放。传递数据结构需要进行汇编的调试以保证正确。GL5110不允许传递数据结构,应该传递数据结构的指针。GL5110不允许使用浮点数。
3. 当参数个数超过4个时,用栈空间进行传递。事实上,a0,a1,a2,a3表示的参数在栈中也有对应的16字节空间。(是不是有点太浪费栈了...)
4. 返回32位数在v0,64位时为v0和v1。
三、栈帧结构
熟悉栈帧结构将极大地帮助汇编调试和C/汇编混合编程。其栈帧结构如下图:
1. 交界处的(>=16字节)参数空间是未定义的,其在被调用函数的入口处,这4个参数是和a0,a1,a2,a3寄存器是相对应的。但栈里面的参数空间的值却是未定义的,即里面的值与a0,a1,a2,a3的值并不相等。
2. 超过4个参数时会把其他的参数内容放到outgoing args域。
3. 被调用函数的栈帧结构并不包括调用函数的参数空间,其一般包括3个域:局部变量域(非强制)、寄存器保存域(非强制)和参数空间(非叶子函数是强制的,叶子函数非强制)。参数空间至少16字节,加上一般ra也是要保存的,而sp预留的空间必须是8的倍数,故一个栈帧结构至少耗掉24字节。栈浪费空间比较严重,所以建议模块开发人员在编程时有意识减少调用层次,扁平化调用也许更适合一些。
4. 一般在函数的入口,编译器就会给这个函数预留好栈帧的空间,所以可以理解成编译器是明确该函数体的行为之后才进行空间预留的。