数组的 gcc 内联 asm 输入和输出参数?

gcc inline asm input and output parameters for arrays?

据我了解,在编写 gcc 风格的内联 asm 时,您必须非常具体和准确地处理所有输入和输出参数(和破坏),以便编译器将 确切地 知道如何为您的代码分配寄存器,以及它可以假设这些寄存器的值以及 asm 代码可能读取 and/or 修改的任何内存。编译器使用此信息来尽可能地优化周围的代码(如果它认为它对任何东西都没有影响,甚至可以完全删除内联汇编)。如果对此不够具体,可能会导致不正确的行为,因为编译器是根据您不正确的规范做出假设的。

我有点不清楚在涉及数组时我应该如何指定我的 asm 正在读取和写入的内容。如果我不告诉编译器我正在读取 and/or 写入整个数组,它可能会做出错误的假设并以导致错误行为的方式优化代码。

假设我有两个 unsigned int 大小为 N 的数组,比方说 array1array2,我的 asm 代码读取两个数组,并写入新数据进入 array1。这是告诉编译器这件事的正确方法吗?

asm("some asm here using %[array1] and %[array2]"
    : "+m"(*(unsigned(*)[N])array1)
    : [array1]"r"(array1), [array2]"r"(array2),
      "m"(*(unsigned(*)[N])array1),
      "m"(*(unsigned(*)[N])array2)
    : /* possible clobbers, like "cc" */);

这至少使我当前的代码可以正常工作,但我不能 100% 确定这是否正是应该如何完成的。 (编译器是否仅在 asm 代码字符串中实际使用这些参数时才将寄存器分配给输入和输出参数?换句话说,那些仅仅为了告诉编译器我们正在读取和写入它们的整体而存在的额外输入和输出不会导致编译器不必要地为它们分配寄存器或其他东西?)

gcc 自己的文档提到了输出数组的语法,但它似乎没有提到输入数组,所以我在这里只是胡乱猜测。

是的,在我看来是正确的,除了 "+m" 使同一数组的 "m" 输入变得多余。仅对 read/write 数组使用 "+m",对只读数组使用 "m"。但是使用与您正在执行的相同的转换为数组。

分离输入和 "=m" 输出操作数在理论上可以告诉编译器它可以使用你的 asm 作为复制和操作(所以不要这样做,除非你确实使用不同的指针指向读取输入并写入输出)。虽然与标量不同,但我认为 GCC 不会发明数组的新副本。但是 "+m" 意味着就地修改,所以编译器没有那个选项。


参见(这个问题几乎是那个问题的重复)。它显示了一个使用任意长度 *(const char (*)[]) 输入的输入数组示例。实际上,如果 [N] 不是编译时常量,它似乎会被忽略(视为无界),这意味着可以访问整个对象。例如gcc 不会使用 (int (*)[N]) 围绕 asm 语句优化或将存储重新排序为 arr[N+1] 除非 N 是编译时常量。

另请注意,如果您的输入确实是 C array,而不仅仅是指针,则您不需要任何转换。 int arr[1024] 作为 "m"(arr) 输入 确实 表示整个数组,并且不会衰减到内存中的指针。


(Does the compiler assign registers to input and output parameters only if those parameters are actually used in the asm code string?

不,操作数的寄存器分配与它们是否实际填充到模板中是分开的。

GCC 不必区分可用作 %%rax(而不是 %0)的 "a" 输入与模板所在的 "r" 输入必须使用 %0%[name] 因为它不知道编译器可能会选择什么。


gcc's own documentation mentions that syntax for an output array, but it doesn't seem to make any mention about input arrays, so I'm just making a wild guess here.

完全相同,是的,需要它。

如果没有适当的虚拟输入来覆盖您的数组(或 "memory" 破坏),则可能会使用您的 asm 语句消除死存储或对存储进行重新排序。 (例如foo[2] = 1; asm(); foo[2] = 3;可以把第一个店往后挪,或者把第二个店往前挪,只做一个。