内联汇编中的非硬编码暂存寄存器
这不是对原始问题的直接答案,但是自从我在这种情况下一直在谷歌搜索并且自从https://stackoverflow.com/a/6683183/895245被接受以来,我将尝试提供一个可能的改进该答案。
改进如下:您应该尽可能避免对暂存寄存器进行硬编码,以便为寄存器分配器提供更多自由。
因此,作为一个在实践中无用的教育示例(可以在单个 中完成lea (%[in1], %[in2]), %[out];
),以下硬编码的暂存寄存器代码:
坏的.c
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in1 = 0xFFFFFFFF;
uint64_t in2 = 1;
uint64_t out;
__asm__ (
"mov %[in2], %%rax;" /* scratch = in2 */
"add %[in1], %%rax;" /* scratch += in1 */
"mov %%rax, %[out];" /* out = scratch */
: [out] "=r" (out)
: [in1] "r" (in1),
[in2] "r" (in2)
: "rax"
);
assert(out == 0x100000000);
}
如果您改为使用此非硬编码版本,则可以编译为更有效的东西:
好.c
#include <assert.h>
#include <inttypes.h>
int main(void) {
uint64_t in1 = 0xFFFFFFFF;
uint64_t in2 = 1;
uint64_t out;
uint64_t scratch;
__asm__ (
"mov %[in2], %[scratch];" /* scratch = in2 */
"add %[in1], %[scratch];" /* scratch += in1 */
"mov %[scratch], %[out];" /* out = scratch */
: [scratch] "=&r" (scratch),
[out] "=r" (out)
: [in1] "r" (in1),
[in2] "r" (in2)
:
);
assert(out == 0x100000000);
}
因为编译器可以自由选择它想要的任何寄存器,而不仅仅是rax
,
请注意,在此示例中,我们必须将临时标记为早期 clobber 寄存器,&
以防止将其作为输入放入相同的寄存器中,我已在以下位置更详细地解释了这一点:何时在扩展的 GCC 内联汇编中使用 earlyclobber 约束? 这个例子在我没有测试的实现中也碰巧失败了&
。
在 Ubuntu 18.10 amd64、GCC 8.2.0 中测试,编译并运行:
gcc -O3 -std=c99 -ggdb3 -Wall -Werror -pedantic -o good.out good.c
./good.out
GCC 手册6.45.2.6 “Clobbers and Scratch Registers”中也提到了非硬编码的暂存寄存器,尽管它们的例子对于普通人来说太多了,无法立即接受:
与通过clobbers 分配固定寄存器来为asm 语句提供临时寄存器不同,另一种方法是定义一个变量并使其成为早期的clobber 输出,如下例中的a2 和a3 所示。这给了编译器寄存器分配器更多的自由。您还可以定义一个变量并将其作为与输入相关联的输出,如 a0 和 a1,分别与 ap 和 lda 相关联。当然,对于绑定输出,您的 asm 在修改输出寄存器后不能使用输入值,因为它们是同一个寄存器。更重要的是,如果您在输出中省略 early-clobber,如果 GCC 可以证明它们在进入 asm 时具有相同的值,那么 GCC 可能会将相同的寄存器分配给另一个输入。这就是为什么 a1 有一个 early-clobber。它的绑定输入,可以想象,lda 的值是 16,并且没有早期的 clobber,它与 %11 共享相同的寄存器。另一方面,ap 不能与任何其他输入相同,因此不需要 a0 上的 early-clobber。在这种情况下也是不可取的。a0 上的早期破坏会导致 GCC 为“m”分配一个单独的寄存器((const double ( )[]) ap) 输入。请注意,将输入绑定到输出是设置由 asm 语句修改的初始化临时寄存器的方法。GCC 假定未绑定到输出的输入不变,例如下面的“b”(16) 将 %11 设置为 16,如果碰巧需要值 16,GCC 可能会在以下代码中使用该寄存器。如果在使用暂存器之前消耗了可能共享同一寄存器的所有输入,您甚至可以使用普通 asm 输出作为暂存器。被 asm 语句破坏的 VSX 寄存器可以使用这种技术,除了 GCC 对 asm 参数数量的限制。
static void
dgemv_kernel_4x4 (long n, const double *ap, long lda,
const double *x, double *y, double alpha)
{
double *a0;
double *a1;
double *a2;
double *a3;
__asm__
(
/* lots of asm here */
"#n=%1 ap=%8=%12 lda=%13 x=%7=%10 y=%0=%2 alpha=%9 o16=%11\n"
"#a0=%3 a1=%4 a2=%5 a3=%6"
:
"+m" (*(double (*)[n]) y),
"+&r" (n), // 1
"+b" (y), // 2
"=b" (a0), // 3
"=&b" (a1), // 4
"=&b" (a2), // 5
"=&b" (a3) // 6
:
"m" (*(const double (*)[n]) x),
"m" (*(const double (*)[]) ap),
"d" (alpha), // 9
"r" (x), // 10
"b" (16), // 11
"3" (ap), // 12
"4" (lda) // 13
:
"cr0",
"vs32","vs33","vs34","vs35","vs36","vs37",
"vs40","vs41","vs42","vs43","vs44","vs45","vs46","vs47"
);
}