考虑以下C和(ARM)程序集片段,它将使用GCC编译:
{{1}}
在这个例子中,我在循环外初始化一些SIMD寄存器,然后让C处理循环逻辑,在循环中使用这些初始化寄存器。
这适用于某些测试代码,但我担心编译器会破坏代码段之间的寄存器。有没有办法确保这不会发生?我是否可以推断出有关将在代码段中使用的寄存器类型的任何保证(在这种情况下,没有SIMD寄存器会被破坏)?
答案 0 :(得分:3)
一般来说,在gcc中没有办法做到这一点; clobbers只保证寄存器将在asm调用周围保留。如果需要确保寄存器保存在两个asm部分之间,则需要将它们存储在第一个内存中,然后在第二个中重新加载。
答案 1 :(得分:3)
编辑:经过多次摆弄后,我得出结论,使用下面描述的策略比我最初想的更难解决。
问题在于,特别是当使用所有寄存器时,没有什么可以阻止第一个寄存器存储覆盖另一个寄存器。使用可以优化的直接内存写入是否有一些技巧我不知道,但初始测试会建议编译器仍然可以选择破坏尚未存储的寄存器
目前,在我获得更多信息之前,我将此答案取消标记为正确,并且在一般情况下,此答案可能会被视为错误。我的结论是,寄存器的这种本地保护需要在编译器中提供更好的支持才能有用
绝对可以做到这一点。借鉴@PeterCordes的评论以及docs和一些有用的错误报告(gcc 41538和37188),我提出了以下解决方案。
使其有效的一点是使用临时变量来确保寄存器得到维护(逻辑上,如果循环破坏了它们,那么它们将被重新加载)。在实践中,临时变量被优化掉,这从检查结果asm中可以清楚地看出。
// d0 and d1 map to the first and second values of q0, so we use
// q0 to reduce the number of tmp variables we pass around (instead
// of using one for each of d0 and d1).
register float32x4_t data __asm__ ("q0");
register float32x4_t output __asm__ ("q12");
float32x4_t tmp_data;
float32x4_t tmp_output;
__asm__ __volatile__ (
"vldmia.64 %[data_addr]!, {d0-d1}\n\t"
"vmov.f32 %q[output], #0.0\n\t"
: [data_addr] "+r" (data_addr),
[output] "=&w" (output),
"=&w" (data) // we still need to constrain data (q0) as written to.
::);
// Stash the register values
tmp_data = data;
tmp_output = output;
for(int n=0; n<10; ++n){
// Make sure the registers are loaded correctly
output = tmp_output;
data = tmp_data;
__asm__ __volatile__ (
"vadd.f32 %[output], %[output], q0\n\t"
"vldmia.64 %[data_addr]!, {d0-d1}\n\t"
: [data_addr] "+r" (data_addr),
[output] "+w" (output),
"+w" (data) // again, data (q0) was written to in the vldmia op.
::);
// Remember to stash the registers again before continuing
tmp_data = data;
tmp_output = output;
}
有必要指示编译器在每个asm输出约束块的最后一行写入q0
,因此它不认为它可以重新排序data
寄存器的存储和重新加载导致asm块获得无效值。