如何理解这个用于 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-reversed。 x
代表索引。
在 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 优化可能会使您的程序在没有源代码级更改的情况下出现错误。
#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
.
这基本上是在传输消息缓冲区时对缓冲区进行交换。这句话让我很疑惑(因为我对c中的嵌入式汇编代码不熟悉)。这是一个 power pc 指令
#define ASMSWAP32(dest_addr,data) __asm__ volatile ("stwbrx %0, 0, %1" : : "r" (data), "r" (dest_addr))
除了因为 bug 不安全之外,这个宏的效率也低于编译器为您生成的宏。
stwbrx
=store word byte-reversed。 x
代表索引。
在 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 优化可能会使您的程序在没有源代码级更改的情况下出现错误。
#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
.