GCC内联汇编副作用

GCC内联汇编副作用,gcc,assembly,x86,inline-assembly,Gcc,Assembly,X86,Inline Assembly,有人能给我解释一下(换句话说)GCC的以下部分吗 这是一个虚构的平方和指令,它将两个指针指向内存中的浮点值,并生成浮点寄存器输出。请注意,x和y在asm参数中都出现两次,一次用于指定访问的内存,一次用于指定asm使用的基址寄存器。这样做通常不会浪费寄存器,因为GCC可以将同一个寄存器用于这两个目的。但是,在此asm中同时使用%1和%3来表示x并期望它们是相同的是愚蠢的。事实上,%3很可能不是寄存器。它可能是x指向的对象的符号内存引用 这是一条虚构的*z++=*x++**y++指令。请注意,x、y

有人能给我解释一下(换句话说)GCC的以下部分吗

这是一个虚构的平方和指令,它将两个指针指向内存中的浮点值,并生成浮点寄存器输出。请注意,x和y在asm参数中都出现两次,一次用于指定访问的内存,一次用于指定asm使用的基址寄存器。这样做通常不会浪费寄存器,因为GCC可以将同一个寄存器用于这两个目的。但是,在此asm中同时使用%1和%3来表示x并期望它们是相同的是愚蠢的。事实上,%3很可能不是寄存器。它可能是x指向的对象的符号内存引用

这是一条虚构的*z++=*x++**y++指令。请注意,x、y和z指针寄存器必须指定为输入/输出,因为asm会修改它们

在第一个示例中,在输入操作数中列出
*x
*y
有什么意义?同一文件规定:

特别是,如果不将输入操作数指定为输出操作数,则无法指定对其进行修改

在第二个示例中,为什么要使用输入操作数部分?它的任何操作数都不会在汇编语句中使用

作为奖励,我们如何才能从SO post更改以下示例,从而不需要使用
volatile
关键字

void swap_2 (int *a, int *b)
{
int tmp0, tmp1;

__asm__ volatile (
    "movl (%0), %k2\n\t" /* %2 (tmp0) = (*a) */
    "movl (%1), %k3\n\t" /* %3 (tmp1) = (*b) */
    "cmpl %k3, %k2\n\t"
    "jle  %=f\n\t"       /* if (%2 <= %3) (at&t!) */
    "movl %k3, (%0)\n\t"
    "movl %k2, (%1)\n\t"
    "%=:\n\t"

    : "+r" (a), "+r" (b), "=r" (tmp0), "=r" (tmp1) :
    : "memory" /* "cc" */ );
}
void swap_2(int*a,int*b)
{
int-tmp0,tmp1;
__反复无常(
“movl(%0),%k2\n\t”/*%2(tmp0)=(*a)*/
“movl(%1),%k3\n\t”/*%3(tmp1)=(*b)*/
cmpl%k3,%k2\n\t

“jle%=f\n\t”/*如果(%2在第一个示例中,
*x
*y
必须列为输入操作数,以便GCC知道指令的结果依赖于它们。否则,GCC可以将存储移动到
*x
*y
通过内联程序集片段,这将访问未初始化的内存。com可以看到这一点在这个例子中:

double
f (void)
{
  double result;
  double a = 5;
  double b = 7;
  double *x = &a;
  double *y = &b;
  asm ("sumsq %0, %1, %2"
       : "+X" (result)
       : "r" (x), "r" (y) /*, "m" (*x), "m" (*y)*/);
  return result;
}
其结果是:

f:
    leaq    -16(%rsp), %rax
    leaq    -8(%rsp), %rdx
    pxor    %xmm0, %xmm0
#APP
# 8 "t.c" 1
    sumsq %xmm0, %rax, %rdx
# 0 "" 2
#NO_APP
    ret
这两个
leaq
指令只是将寄存器设置为指向堆栈上未初始化的红色区域。赋值已消失

第二个例子也是如此


我认为您可以使用相同的技巧来消除
易失性
。但我认为这里实际上没有必要,因为已经有一个
“memory”
clobber,它告诉GCC内存是从内联汇编读取或写入的。

我的猜测是
“m”(*x),“m”(*y)
被添加为输入操作数,以确保在调用扩展程序集模板之前将x和y的值实现到内存中。如果禁用它们,并通过寄存器传递地址,则无法保证代码生成器实际将x和y的数据写入内存(由两个寄存器指向)。
“m”(*x),“m”(*y)
输入约束确保在执行内联程序集之前,x和y的值都在内存中。这种情况可能发生在某些代码结构和优化处于启用状态时。上面的Mu注释也适用于第二个示例(vecmul)。如果允许虚构的指令将内存操作数作为参数,则这不会是一个问题。交换代码效率非常低,但从技术上讲,它一开始甚至不需要volatile修饰符。事实上,在优化时,如果volatile存在,并且将swap_2内联到其他指令中,它可能会生成效率较低的代码函数。不需要volatile,因为程序集模板的所有副作用都由输入、输出和clobber操作数来考虑。另一方面,实际上可以在程序集模板内无代码交换两个输入的数据,并严格使用约束来完成工作。这在答案是:。这是内联程序集更高级的用法。在交换代码中,根本不需要volatile。内联程序集的所有副作用都在约束中考虑。“内存”“clobber将确保在执行程序集模板之前将数据实现到内存中。顺便说一句,
volatile
并不意味着您可以省略
内存”
clobber.
asm volatile
仅仅意味着它不是输入的纯函数,也就是说,即使输出未使用,它也需要按照源代码所说的次数运行,而不是与其他
asm volatile
@Florian Weimer一起重新排序非常感谢。您的示例很好地解释了这一点。出于好奇,您选择了哪些选项用于获取此干净的asm代码?我删除了不相关的行。但是您可以使用
-fno异步展开表-O2-S-o-
获得几乎相同的效果。@Florian Weimer
gcc-fno堆栈保护器-fno异步展开表-O2-S-o-
完成了此工作。谢谢。
double
f (void)
{
  double result;
  double a = 5;
  double b = 7;
  double *x = &a;
  double *y = &b;
  asm ("sumsq %0, %1, %2"
       : "+X" (result)
       : "r" (x), "r" (y) /*, "m" (*x), "m" (*y)*/);
  return result;
}
f:
    leaq    -16(%rsp), %rax
    leaq    -8(%rsp), %rdx
    pxor    %xmm0, %xmm0
#APP
# 8 "t.c" 1
    sumsq %xmm0, %rax, %rdx
# 0 "" 2
#NO_APP
    ret