在一些对代码执行时间要求很严格的算法,例如很多个点的FFT、IFFT中,汇编代码的高效仍然是C/C++所不能替代的。这些就涉及到了C/C++代码与汇编代码的接口问题,在此我们就来了解一下它们是如何交互的。首先要遵循以下的九大原则:
1. 所有的函数(不管是C/C++的还是汇编的)都要遵循特定的寄存器约定
寄存器约定用来规定编译器如何使用寄存器,主要指的CPU/FPU的寄存器(因为它们在程序运行中是反复使用的),并且在函数调用前后如何保存寄存器的值。保存寄存器值的方法分为在函数入口处保存和在调用时保存,前者是由被调用的函数来完成的,后者则是由调用别的函数的函数来完成的。在TMS320C28x编译器中,使用如下的规则,如表1、表2所示。通过表1、2,我们还可以对CPU/FPU的寄存器有进一步的理解,这样以后再看到寄存器的名字时就不至于一头雾水了。
表1 CPU寄存器使用和保留的约定
表2 FPU寄存器使用和保留的约定
编译器对CPU的状态寄存器ST中的某些位也有一定的约定,如表3、表4所示;没有列在表中的状态寄存器的位则不受编译器的影响。对其中的某些位,编译器会假设它们在函数调用或者返回时具有特定的假想值;在系统初始化建立C语言的实时运行环境时,这些位也会被初始化为特定的假象值。
表3 CPU状态寄存器ST的约定
表4 FPU状态寄存器STF(1)的约定
(1)未使用的STF寄存器的位,读取为0,写无效。
(2)使用MOVST0寄存器的时候,STF寄存器的特定标志位可以复制到ST寄存器中。
(3)LVF和LUF可以连接到PIE(外设中断扩展)中,从而触发上溢/下溢中断,在调试过程中可以帮助我们诊断浮点数的溢出问题。
(4)如果RNDF32或者RNDF64是0,则取整模式为截断,否则是近似到最近的整数。
2. SP的使用、结构体的返回方式
3. 长整形和浮点型在保存时,LSW(Least Significant Word,低有效字)保存在地址中。
4. 除了全局变量的初始化以外,汇编模块不应使用.cinit段。这是因为C/C++的启动代码假设在.cinit段中只包含了初始化表,如果把其它的信息存入这个段中,将导致不可预测的运行结果。
5. 编译器会自动为所有的标识符前面加上一个下划线,比如在C/C++中的某个函数名字为xxx,则编译成汇编代码时,它的名称变为_xxx。在这种转换规则下,我们在C/C++中编程时,函数、变量的名称就不能以下划线“_”来开头了。
6. 链接器会自动为外部目标分配链接名,所以在用汇编代码编写程序的时候,也要使用相同的名字才能被链接器正确识别。通用的规则是:
_func__F parmcodes
例如,我们有一个C++函数:
int foo(int i){ } //global C++ function
转换为汇编之后变为:
_foo__Fi ;foo为函数名,它有一个int类型的参数
7. 为了让汇编代码中的变量和函数能在C/C++代码中调用,在汇编中编程中需要使用.def或者.global指令对它们进行定义。如果希望从汇编代码中调用C/C++中的函数或者变量,则在汇编代码中需要使用.ref或者.global指令对它们进行定义。
8. 因为复位之后,编译的C/C++代码运行PAGE0模式下,所以如果在汇编函数中把PAGE0位修改成了1,在返回时一定要记得把它恢复为0。
注:PAGE0位为0时,使用栈寻址模式,为1时则使用直接寻址模式。
9. 如果在汇编代码中使用了结构,并且希望在C代码中使用extern struct指令来调用它,则这个结构应当被模块化(在汇编指令的选项中启用模块化标志,并为其分配连续的存储空间),从而优化DP的加载。在汇编代码中使用.usect或者.bss指令可以来指定结构的模块化,使用方法为:
.usect "section name", size in words[, blocking flag[, alignment flag[, type] ] ]或者.bss symbol, size in words[, blocking flag[, alignment flag[, type]]]