如何理解这个用于 PowerPC stwbrx 的 GNU C 内联汇编宏

How to understand this GNU C inline assembly macro for PowerPC stwbrx

这基本上是在传输消息缓冲区时对缓冲区进行交换。这句话让我很疑惑(因为我对c中的嵌入式汇编代码不熟悉)。这是一个 power pc 指令

#define ASMSWAP32(dest_addr,data) __asm__ volatile ("stwbrx %0, 0, %1" : : "r" (data), "r" (dest_addr))

除了因为 bug 不安全之外,这个宏的效率也低于编译器为您生成的宏。


stwbrx=store word byte-reversedx 代表索引。

在 GNU C 中您不需要内联汇编,您可以在其中使用 __builtin_bswap32 并让编译器为您发出此指令。

void swapstore_asm(int a, int *p) {
    ASMSWAP32(p, a);
}

void swapstore_c(int a, int *p) {
    *p = __builtin_bswap32(a);
}

使用 gcc4.8.5 -O3 -mregnames 编译,我们从两个函数中得到相同的代码 (Godbolt compiler explorer):

swapstore:
    stwbrx %r3, 0, %r4
    blr
swapstore_c:
    stwbrx %r3,0,%r4
    blr

但是使用更复杂的地址(存储到 p[off],其中 off 是一个整数函数 arg),编译器知道如何使用两个寄存器输入,而您的宏强制编译器在单个寄存器中有地址:

void swapstore_offset(int a, int *p, int off) {
     = __builtin_bswap32(a);
}

swapstore_offset:
    slwi %r5,%r5,2              # *4 = sizeof(int)
    stwbrx %r3,%r4,%r5          # use an indexed addressing mode, with both registers non-zero
    blr

swapstore_offset_asm:
    slwi %r5,%r5,2
    add %r4,%r4,%r5            # extra instruction forced by using the macro
    stwbrx %r3, 0, %r4
    blr

顺便说一句,如果您在理解 GNU C 内联 asm 模板时遇到困难,查看编译器的 asm 输出可能是了解替换内容的有用方法。有关阅读编译器 asm 的更多信息,请参阅 输出。


另请注意,此宏存在错误:它缺少 "memory" 商店 的破坏。是的,您仍然需要 asm volatile。编译器不会假设 *dest_addr 被修改,除非你告诉它,所以它可以在这个 insn 之前提升一个 *dest_addr 的非易失性负载,或者更可能是一个真正的问题,下沉一个在它之后存储。 (例如,如果您在使用此指令存储之前将缓冲区置零,编译器实际上可能会在 这条指令之后将 置零。)

而不是 "memory" 破坏(并且还遗漏了 volatile),您可以告诉编译器 您修改了哪个 内存位置 =m" (*dest_addr) 操作数,作为虚拟操作数或对寻址模式有限制,因此您可以将其用作 reg+reg。 (IDK PPC 足以知道 "=m" 通常扩展到什么。)

在大多数情况下,这个错误不会咬你,但它仍然是一个错误。升级您的编译器版本或使用 link-time 优化可能会使您的程序在没有源代码级更改的情况下出现错误。

这种事情就是为什么https://gcc.gnu.org/wiki/DontUseInlineAsm

另见 https://whosebug.com/tags/inline-assembly/info

#define ASMSWAP32(dest_addr,data) ...

这部分应该清楚

__asm__ volatile ( ... : : "r" (data), "r" (dest_addr))

这是实际的内联汇编:

两个值被传递给汇编代码;汇编代码没有返回任何值(这是实际汇编代码后的冒号)。

两个参数都在寄存器中传递 ("r")。表达式 %0 将被包含 data 值的寄存器替换,而表达式 %1 将被包含 dest_addr 值的寄存器替换(这将在这种情况下是一个指针)。

这里的volatile表示汇编代码必须在此时执行,不能移动到其他地方。

所以如果你在C源代码中使用下面的代码:

ASMSWAP(&a, b);

...将生成以下汇编代码:

# write the address of a to register 5 (for example)
...
# write the value of b to register 6
...
stwbrx 6, 0, 5

所以stwbrx指令的第一个参数是b的值,最后一个参数是a的地址。

stwbrx x, 0, y

该指令将寄存器x中的值写入寄存器y中存储的地址;但是它将值写入 "reverse endian" (在大端 CPU 上它写入值 "little endian".

以下代码:

uint32 a;
ASMSWAP32(&a, 0x12345678);

... 因此应该导致 a = 0x78563412.