GCC内联汇编中Early Clobber与寄存器共享的技术疑问
GCC扩展汇编Early-Clobber约束疑问解答
文档示例与描述
GCC文档中的PowerPC扩展汇编示例:
static void dgemv_kernel_4x4 (long n, const double *ap, long lda, const double *x, double *y, double alpha) { double *a0; double *a1; double *a2; double *a3; __asm__ ( /* lots of asm here */ "#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n" "#a0=%3 a1=%4 a2=%5 a3=%6" : "+m" (*(double (*)[n]) y), "+&r" (n), // 1 "+b" (y), // 2 "=b" (a0), // 3 "=&b" (a1), // 4 "=&b" (a2), // 5 "=&b" (a3) // 6 : "m" (*(const double (*)[n]) x), "m" (*(const double (*)[]) ap), "d" (alpha), // 9 "r" (x), // 10 "b" (16), // 11 "3" (ap), // 12 "4" (lda) // 13 : "cr0", "vs32","vs33","vs34","vs35","vs36","vs37", "vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47" ); }文档描述:
... 另一方面,ap不能与其他任何输入相同,因此无需为a0添加early-clobber约束。在这种情况下添加反而不可取。为a0添加early-clobber会导致GCC为"m" (*(const double (*)[]) ap)输入分配独立寄存器。请注意,将输入与输出绑定是设置由asm语句修改的初始化临时寄存器的方式。GCC假设未与输出绑定的输入保持不变...
疑问
- 代码中
"m" (*(const double (*)[]) ap)与"=b" (a0)并无关联,"=b" (a0)与"3" (ap)共享寄存器(存储输入参数地址),而前者是ap首元素内容,为何为a0加early-clobber会影响该内存输入? - 即便GCC为该内存输入分配新寄存器,因
"=b" (a0)与"3" (ap)已绑定,仍可通过a0的寄存器读写,这会有什么问题?
解答
问题1:early-clobber影响内存输入寄存器分配的原因
首先要明确,"m" (*(const double (*)[]) ap)这个内存约束,GCC并非只能直接访问内存——它有权选择先把ap的地址加载到寄存器,再通过寄存器间接访问内存。
原本,"3" (ap)把输入ap绑定到输出a0的寄存器,GCC可以复用这个寄存器来访问"m" (*(const double (*)[]) ap)对应的内存(毕竟这个内存的地址就是ap,用同一个寄存器完全合理)。但给a0的=b约束加上&(early-clobber)后,GCC会判定这个输出寄存器会在汇编代码早期被覆盖,不能再被任何输入依赖。因此GCC会认为,这个寄存器无法再用来访问那个内存输入,只能额外分配一个新寄存器来存储ap的地址,专门用于内存访问。
简单来说:内存输入的访问依赖ap的地址,原本可以复用a0的寄存器,但early-clobber让GCC放弃了这种复用,被迫多分配寄存器。
问题2:额外分配寄存器的负面影响
- 寄存器压力增大:PowerPC通用寄存器数量有限,额外分配寄存器会导致GCC可能需要把其他变量 spill 到内存,直接降低代码执行效率。
- 汇编逻辑与预期不符:原代码的汇编注释明确依赖
a0的寄存器(%3)来对应ap的地址(ap=%8=%12)。如果GCC为内存输入分配了新寄存器,汇编代码里访问ap指向内存时,可能会用到这个新寄存器而非a0,导致内存访问地址错误,或者需要额外指令同步寄存器,增加不必要的开销。 - 违反GCC的假设逻辑:GCC默认未绑定到输出的输入会保持不变。额外分配的寄存器存储的是
ap的地址(属于输入),GCC会认为它不会被修改,但原代码中a0的寄存器是输出,会被修改,这会导致逻辑不一致,进而引发未定义行为。
内容的提问来源于stack exchange,提问作者Wei Li
相关产品推荐
相关产品推荐

