Gcc 混合内联汇编与C代码-如何保护寄存器和最小化内存访问
我有一个我想在汇编中编写的例程,但我需要调用C函数来获取一些我需要处理的数据。在某些情况下,我可以预消化数据并加载一个带有指针的寄存器,但在其他情况下,我必须调用完整函数,因为可能的数据集太大。无法修改这些函数,因为它们是其他人的代码,并且对于其他代码段,其接口需要保持不变。其中一些驻留在共享库中,尽管有些是通过头文件(我无法更改)实现的内联函数 我可以使用asm构造将局部变量分配给寄存器:Gcc 混合内联汇编与C代码-如何保护寄存器和最小化内存访问,gcc,x86-64,inline-assembly,gnu-assembler,Gcc,X86 64,Inline Assembly,Gnu Assembler,我有一个我想在汇编中编写的例程,但我需要调用C函数来获取一些我需要处理的数据。在某些情况下,我可以预消化数据并加载一个带有指针的寄存器,但在其他情况下,我必须调用完整函数,因为可能的数据集太大。无法修改这些函数,因为它们是其他人的代码,并且对于其他代码段,其接口需要保持不变。其中一些驻留在共享库中,尽管有些是通过头文件(我无法更改)实现的内联函数 我可以使用asm构造将局部变量分配给寄存器: register int myReg asm( "%r13" ); 我担心如果我直接在汇编中操作%r1
register int myReg asm( "%r13" );
我担心如果我直接在汇编中操作%r13,调用一个C函数,然后返回,它将需要从内存中刷新,或者更糟糕的是完全被覆盖。对于某些ABI,我自己直接按下/弹出寄存器也不安全,对吗?我在Linux上使用x86-64
我现在所做的似乎是使用-g-O0,但我担心当我打开C代码的优化时,它会开始触及我希望得到保护的寄存器
通常,我的代码流如下所示:
asm( "movq %[src], %%r13" : : [src] "m" (myVariablePointer) : "r13" );
localVariable1 = callSomeCfunction( stuff );
storageLocation = index * sizeof( longTermStorageItem );
longTermStorage[storageLocation] = localVariable1;
// some intermediate registers need to be used here for dereferences and math
switch ( localVariable1 )
{
case CONSTANT_VAL_A:
C_MACRO_LOOP_CONSTRUCT
{
asm( "movdqa (%r13), %xmm0\n"
// ... do some more stuff
} C_MACRO_LOOP_CONSTRUCT_ENDING
break;
case CONSTANT_VAL_B:
// ... and so forth
}
“C#u MACRO_LOOP#CONSTRUCT”是从外部头文件中定义的,其中包含“for”循环,需要在过程中取消引用某些指针等,并将迭代器存储在局部变量中
因此,我关心的是如何确保在所有这些内容中保留%r13。到目前为止,编译器还没有触及它,但我相信这更多的是运气,而不是设计。保存价值本身并不是我唯一关心的。如果可能的话,我想把它留在我放的登记册里。将其移出到本地/堆栈存储并频繁返回会降低我的性能
有没有一种方法可以更好地保护编译器/优化器中的一小部分寄存器
其他信息
这就是为什么我要这么做。请看下面的代码:
#include <emmintrin.h>
#include <stdio.h>
__m128d buffer[100];
int main( void )
{
unsigned long long *valPtr;
register __m128d val;
register __m128d *regPtr;
#ifdef FORCED
asm( "movq %[src], %%r13" :
:
[src] "r" (buffer) );
asm( "pcmpeqd %[src], %[dst]" :
[dst] "=x" (val) :
[src] "x" (val) );
asm( "movdqa %[src], (%%r13)" : :
[src] "x" (val) );
asm( "movdqa %[src], 16(%%r13)" : :
[src] "x" (val) );
#else
asm( "pcmpeqd %[src], %[dst]" :
[dst] "=x" (val) :
[src] "x" (val) );
asm( "movdqa %[src], %[dst]" :
[dst] "=X" (buffer) :
[src] "x" (val) );
asm( "movdqa %[src], %[dst]" :
[dst] "=X" (buffer+1) :
[src] "x" (val) );
#endif
valPtr = (unsigned long long *)buffer;
printf( "OUTPUT: [0] %016llx%016llx, [1] %016llx%016llx\n",
valPtr[0], valPtr[1], valPtr[2], valPtr[3] );
return 0;
}
所以我想我的问题应该是,我可以使用带有适当约束的索引寻址模式吗?我试过“m”、“X”和“o”。没有区别。如果我尝试将偏移量拉入部件并拉出参数,如下所示:
asm( "movdqa %[src], 16(%[dst])" :
[dst] "=m" (buffer) :
[src] "x" (val) );
__m128d buffer[100];
int main( void )
{
register __m128d val;
asm("# val: %0" : "=x" (val)); /* fix "is used uninitialized" warning */
asm( "pcmpeqd %[sval], %[dval]\n\t"
"movdqa %[dval], %[buffer]\n\t"
"movdqa %[dval], %[buffer1]" :
[dval] "=x" (val), [buffer] "=m" (buffer[0]), [buffer1] "=m" (buffer[1]) :
[sval] "x" (val) );
}
GCC的回应是:
/tmp/ccoNwyco.s: Assembler messages:
/tmp/ccoNwyco.s:28: Error: junk `(buffer(%rip))' after expression
你知道如何使用这种寻址模式并消除不必要的指令吗?既然你问了关于附加部分的问题,我将重点讨论这个问题。查看您的第一个#if块:
__m128d buffer[100];
int main( void )
{
register __m128d val;
asm( "movq %[src], %%r13" :
:
[src] "r" (buffer) );
asm( "pcmpeqd %[src], %[dst]" :
[dst] "=x" (val) :
[src] "x" (val) );
asm( "movdqa %[src], (%%r13)" : :
[src] "x" (val) );
asm( "movdqa %[src], 16(%%r13)" : :
[src] "x" (val) );
}
此片段写入r13,而不告诉编译器。那太糟糕了。即使在调用此asm之前在某些局部变量上有一个asm(“r13”),这也会很糟糕。您仍然需要将该局部变量列为输出,然后列为后续ASM的输入。更重要的是,它既让维护人员感到困惑,也没有必要
还有,像这样有多个asm语句是个坏主意。gcc可能不会选择按此顺序保存它们。在这种情况下,我建议采取类似的措施:
asm( "movdqa %[src], 16(%[dst])" :
[dst] "=m" (buffer) :
[src] "x" (val) );
__m128d buffer[100];
int main( void )
{
register __m128d val;
asm("# val: %0" : "=x" (val)); /* fix "is used uninitialized" warning */
asm( "pcmpeqd %[sval], %[dval]\n\t"
"movdqa %[dval], %[buffer]\n\t"
"movdqa %[dval], %[buffer1]" :
[dval] "=x" (val), [buffer] "=m" (buffer[0]), [buffer1] "=m" (buffer[1]) :
[sval] "x" (val) );
}
至于你的#else块:
__m128d buffer[100];
int main( void )
{
register __m128d val;
asm( "pcmpeqd %[src], %[dst]" :
[dst] "=x" (val) :
[src] "x" (val) );
asm( "movdqa %[src], %[dst]" :
[dst] "=X" (buffer) :
[src] "x" (val) );
asm( "movdqa %[src], %[dst]" :
[dst] "=X" (buffer+1) :
[src] "x" (val) );
}
我建议:
__m128d buffer[100];
int main( void )
{
register __m128d val;
asm("# val: %0" : "=x" (val)); /* fix "is used uninitialized" warning */
asm( "pcmpeqd %[sval], %[dval]\n\t"
"movdqa %[dval], (%[sbuffer])\n\t"
"movdqa %[dval], 16(%[sbuffer])" :
[dval] "=x" (val), [buffer] "=m" (buffer), [buffer1] "=m" (buffer[1]) :
[sval] "x" (val), [sbuffer] "r" (buffer));
}
这里有几件事需要注意
__m128d buffer[100];
int main( void )
{
register __m128d val;
for (int x=0; x < 10; x++)
{
asm("# val: %0" : "=x" (val)); /* fix "is used uninitialized" */
asm( "pcmpeqd %[src], %[dst]\n\t"
"movdqa %[src], (%[sbuffer])\n\t" /* buffer[0] */
"movdqa %[src], 16(%[sbuffer])" : /* buffer[1] */
[dst] "=x" (val), [buffer] "=m" (buffer), [buffer1] "=m" (buffer[1]) :
[src] "x" (val), [sbuffer] "r" (buffer));
printf("%d\n", val);
}
}
嗯,它已经从rax变为rbx。这样更好吗?嗯,事实上是这样。在c中调用子例程时,编译器必须遵循一些规则(ABI)。这些规则控制诸如参数传递的位置、返回值的位置、谁清理堆栈,以及(对于我们这里的目的来说,最重要的是)子例程必须保留哪些寄存器(即返回时必须具有相同的值)。这里有一些关于这方面的讨论和有用的链接。值得注意的是,必须保留rbx(对于x86-64)
因此,如果您查看围绕此代码的asm,您会注意到rbx只加载一次(在循环之外)。Gcc知道,如果任何子例程弄脏了rbx,他们会在完成后将其放回原处。此外,由于子例程知道必须保留rbx,因此它们倾向于避免使用它,除非多使用一个寄存器的好处大于保存/恢复它的成本
至于“保留”寄存器并阻止任何子例程使用它的整个想法,我不会说这是不可能的(参见和),但我会说通常这是一个糟糕的想法。x86上的寄存器是非常有用且非常有限的资源。特里伊
.L2:
leaq .LC0(%rip), %rcx
movq %rdi, %rdx
pcmpeqd %xmm6, %xmm0
movdqa %xmm6, (%rbx)
movdqa %xmm6, 16(%rbx)
movapd %xmm0, 32(%rsp)
call printf
subl $1, %esi
jne .L2