如何将汇编语言与C语言整合至DSP
汇编语言设计师的编译器考虑
在编写会与C语言程序代码结合的汇编程序码时,汇编程序设计人员必须了解编译器的惯例和假设。其中有个重要的编译器惯例是函数呼叫惯例,也称为函数参数传递惯例。这个惯例描述了编译器如何在一个函数呼叫另一个函数时传递参数。为了使汇编语言函数能被C语言函数成功呼叫;反之亦然;汇编语言函数必须截取参数,然后将参数发送到由函数呼叫惯例定义的硬件资源上,通常为缓存器或堆栈内存。
汇编程序设计人员还必须了解编译器的缓存器使用惯例。这些惯例将硬件缓存器分成被呼叫者保存(callee-saved;或呼叫者使用, caller-used);以及被呼叫者使用(callee-used;或呼叫者保存,caller-saved)缓存器。编译器假设被呼叫者保存缓存器在函数呼叫过程中保持不变的值,若汇编程序设计人员希望使用这种缓存器,他们必须先将缓存器的值备份,然后在返回到C语言程序代码之前恢复这些缓存器的内容;相反的,被呼叫者使用缓存器被认为在函式呼叫过程中不会保持一定的值。这意味着汇编程序设计人员使用这些缓存器之前无需进行备份,不过他们必须记住,当汇编语言函数呼叫C语言函数时,被呼叫者可以对这些缓存器进行覆写。
图2为一个从CEVA-X1641 DSP核心FFT实作中截取的汇编程序码案例。其中以黄色标示的add指令遵循CEVA-X1641编译器的呼叫惯例,在r0地址缓存器中传递指针参数。标为蓝色的pushd指令用于备份,随后函数会使用的被呼叫者保存缓存器。
除了呼叫惯例和缓存器使用惯例(针对每个编译器下定义),一些编译器在人工编写的汇编程序码方面可能会有一些额外的假设。这些假设通常是针对编译器,因此编译器供货商应该提供完善的数据和说明。例如,一些DSP架构会有内存存取对齐限制,用于这些DSP的编译器通常假设堆栈指针以某个宽度(如32位)对齐,这允许编译器最佳化堆栈的读写作业,并使用机器的全部内存频宽;此外亦要求汇编程序设计人员在呼叫C语言函数前确保堆栈对齐,否则会发生对齐错误的存取。
编译器假设的另外一个例子与人工编写的汇编程序码中特殊指令的位置有关。例如,CEVA-X1641编译器假设一个mov acX, rN指令(将累加器移动到地址缓存器)永远不会作为汇编语言函数的第一条指令。当填充呼叫指令(呼叫一个函数)的延迟槽时,这个假设可提供更佳的指令排程。像这样特殊的假设通常可以用专用编译功能覆盖。
连接C/汇编语言的延伸功能
大多数用于嵌入式平台的编译器,特别是用于DSP程序设计上,都具有丰富的C语言和汇编语言连接功能。其中绝大部份功能不属于标准C语言,因此被称为C语言延伸功能。以下列出的是一些对DSP程序设计更有用的功能。
内嵌汇编语言。这个功能可让程序设计人员将汇编语言指令插入C语言程序代码,当必需使用如装置驱动程序等低阶C语言程序代码直接存取机器资源时,会常使用到该功能。由于在大多数使用该功能的实作中,编译器对所要插入的指令信息所知有限,因此对它们的特性会作出最坏的假设,这种假设可能会妨碍许多编译器最佳化作业。例如,在支持某些指令(并非全部指令)平行处理的架构中,编译器不会将插入指令与其它指令作平行处理,因为这种作法很可能会导致非法指令封包。
将硬件缓存器连结到C变量。将一个硬件缓存器连结到一个C变量时,C语言程序代码中的变量值即反映出硬件缓存器的值;反之亦然。每当C变量被读写时,硬件缓存器也相对地被读写。这个功能在低阶程序代码中很常见,时常与汇编语言指令内嵌功能结合在一起,允许内嵌汇编程序码存取C语言层级的变量。图 3的例子显示了内嵌汇编语言功能(标示为橙色)和硬件缓存器连结功能(标示为紫色)的常见组合。