混合C和汇编及其对寄存器的影响

时间:2016-03-11 14:14:07

标签: c gcc assembly

考虑以下C和(ARM)程序集片段,它将使用GCC编译:

{{1}}

在这个例子中,我在循环外初始化一些SIMD寄存器,然后让C处理循环逻辑,在循环中使用这些初始化寄存器。

这适用于某些测试代码,但我担心编译器会破坏代码段之间的寄存器。有没有办法确保这不会发生?我是否可以推断出有关将在代码段中使用的寄存器类型的任何保证(在这种情况下,没有SIMD寄存器会被破坏)?

2 个答案:

答案 0 :(得分:3)

一般来说,在gcc中没有办法做到这一点; clobbers只保证寄存器将在asm调用周围保留。如果需要确保寄存器保存在两个asm部分之间,则需要将它们存储在第一个内存中,然后在第二个中重新加载。

答案 1 :(得分:3)

编辑:经过多次摆弄后,我得出结论,使用下面描述的策略比我最初想的更难解决。

问题在于,特别是当使用所有寄存器时,没有什么可以阻止第一个寄存器存储覆盖另一个寄存器。使用可以优化的直接内存写入是否有一些技巧我不知道,但初始测试会建议编译器仍然可以选择破坏尚未存储的寄存器

目前,在我获得更多信息之前,我将此答案取消标记为正确,并且在一般情况下,此答案可能会被视为错误。我的结论是,寄存器的这种本地保护需要在编译器中提供更好的支持才能有用

绝对可以做到这一点。借鉴@PeterCordes的评论以及docs和一些有用的错误报告(gcc 4153837188),我提出了以下解决方案。

使其有效的一点是使用临时变量来确保寄存器得到维护(逻辑上,如果循环破坏了它们,那么它们将被重新加载)。在实践中,临时变量被优化掉,这从检查结果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块获得无效值。

相关问题