gcc 在内联 asm 中错误地重用寄存器

gcc incorrectly reusing registers in inline asm

我已经在 Cortex-M4 的 C 程序中实现了一个简单的延迟循环宏:

#define DELAY_CYCLES (F_CPU / 3000000) //F_CPU is 72000000
#define delayUS(n) __asm__ volatile( \
    "1: subs %0, #1       \n" \
    "bne 1b               \n" \
    : /* no outputs */ \
    : "r" (n * DELAY_CYCLES) /* input */ \
    : "0" /* clobbers */ \
)

这会延迟 n 微秒(假设中断被禁用)。大多数情况下,它工作正常。但是,我发现它在使用它两次的函数中无法正常工作:

static void test(uint8_t num) {
    digitalWrite(12, 1);
    delayUS(10);
    digitalWrite(13, 1);
    delayUS(10);
    digitalWrite(12, 0);
    digitalWrite(13, 0);
}

(这是一个实际使用 num 的函数,但在调试此问题时被简化为这个。它也被内联到 main,因此反汇编中的标签。)

这里发生的是对 delayUS() 的第二次调用从未完成。检查生成的程序集显示了问题:

 528:   2701        movs    r7, #1
 52a:   6037        str r7, [r6, #0]    ;digitalWrite(12, 1)
 52c:   23f0        movs    r3, #240    ;delayUS(10); 10 * DELAY_CYCLES = 240
 52e:   3b01        subs    r3, #1
 530:   d1fd        bne.n   52e <main+0x4a>

 532:   4c0d        ldr r4, [pc, #52]
 534:   6027        str r7, [r4, #0]    ;digitalWrite(13, 1)

 536:   3b01        subs    r3, #1      ;delayUS(10), but r3 is still 0
 538:   d1fd        bne.n   536 <main+0x52>

 53a:   2300        movs    r3, #0
 53c:   6033        str r3, [r6, #0]    ;digitalWrite(12, 0)

出于某种原因,gcc 在第二个延迟循环中使用它之前不会重新初始化 r3,因此它不会延迟 240 次迭代(10µs),而是延迟 2^32(大约 3分钟)。

有了这个变体,问题就消失了:

__attribute__((used)) static int dummy;
#define delayUS(n) __asm__ volatile( \
    "1: subs %0, #1       \n" \
    "bne 1b               \n" \
    : "=r" (dummy) /* no outputs */ \
    : "0" (n * DELAY_CYCLES) /* input */ \
    : "0" /* clobbers */ \
)

生成更正确的代码:

 528:   2701        movs    r7, #1
 52a:   23f0        movs    r3, #240    ;r3 = 10 * DELAY_CYCLES
 52c:   6037        str r7, [r6, #0]    ;digitalWrite(12, 1)
 52e:   461a        mov r2, r3          ;r2 = r3

 530:   3a01        subs    r2, #1      ;delayUS(r2)
 532:   d1fd        bne.n   530 <main+0x4c>

 534:   4c0d        ldr r4, [pc, #52]
 536:   6027        str r7, [r4, #0]    ;digitalWrite(13, 1)

 538:   3b01        subs    r3, #1      ;delayUS(r3)
 53a:   d1fd        bne.n   538 <main+0x54>

 53c:   4a0c        ldr r2, [pc, #48]
 53e:   6013        str r3, [r2, #0]    ;digitalWrite(12, 0)

在这里,它正确地意识到延迟循环破坏了它的输入寄存器,因此在没有初始化它的情况下不会重新使用 r3(它使用 r2 作为其中一个循环。 )

那么,当 gcc 列在破坏列表中时,为什么 gcc 不承认以前的版本也破坏了它的输入?

问题是'clobbers'列表是寄存器名称列表,或者特殊字符串"cc"和"memory"。因为没有名为“0”的寄存器,所以在 clobbers 列表中有这个是没有意义的。不幸的是 gcc 没有给你一个警告。相反,正如 gcc 文档所述:

Warning: Do not modify the contents of input-only operands (except for inputs tied to outputs). The compiler assumes that on exit from the asm statement these operands contain the same values as they had before executing the statement. It is not possible to use clobbers to inform the compiler that the values in these inputs are changing. One common work-around is to tie the changing input variable to an output variable that never gets used.

您的第二个示例就是这样做的,这也是它起作用的原因。为了正确起见,您可能还应该将 "cc" 添加到 clobbers 列表(当您修改标志时),您最好删除“0”,因为它没有意义。