我正在尝试熟悉 gnu 内联汇编。我编写了一个单行内联汇编来将 int 重新解释为 float。虽然这会打印正确的结果,但我想知道这是否正确。
#include <stdio.h>
int main() {
int x = 0x80000000; // -0.0
float y;
asm("vmovd %0, %%xmm0"
: "=r"(y)
: "r"(x));
printf("%f\n", y);
}
我是汇编方面的初学者。
仅供参考,内联 asm 是一种糟糕的打字方式,
bit_cast
也就是说,除了作为内联 asm 语法的学习练习之外。只需使用memcpy
。(https://gcc.gnu.org/wiki/DontUseInlineAsm)。如果你确实使用内联汇编,通常不要编写自己的移动指令;使用操作数约束的功能来告诉编译器你想要输入的位置以及你的汇编将输出留在哪里,然后让它发明数据移动。
否,
"=r"
让编译器为该输出操作数选择任意整数寄存器,这将根据周围的代码和优化选项而变化。您正在编写 XMM0,但并未通过操作数或 clobber 告知编译器。%0
是第一个操作数,即输出"=r"
,并且您将其用作的源vmovd
。(这是 AT&T 语法,因此目标在右侧。)因此%1
您实际上没有引用它(源操作数)。编译器通常会为输入和输出操作数选择相同的 GPR(如果您未指定"=&r"
进行早期破坏输出,则适用于在最后一次读取所有输入之前写入输出的情况。)如果将
%0
和%1
放在格式字符串中(例如作为注释),则可以查看编译器的 asm 输出并查看它为该操作数选择了什么。从使用 GCC -O3 针对 Linux (在Godbolt上)的 asm 输出中:
保持函数最小化可以减少我们必须筛选的噪音,以准确找到 GCC 认为仅对 asm 语句本身所必需的内容。x86-64
return
中的浮点数需要将其放在 XMM 寄存器中,并且可以float
直接使用,而不需要将其转换double
为printf
。如果我们
main
使用这个 asm 语句来编译你的程序,我们就会明白为什么它也会在那里工作,因为x
再次y
选择了相同的寄存器(EAX)。您可以
asm("" : "=rx"(y) : "0"(x))
要求输入与输出位于同一个寄存器中,并让编译器选择整数 (r) 或向量 (x) 寄存器。这就像"+r"(y)
允许输入部分使用不同类型的不同变量。如果编译器认为这不是最佳选择,那么可以避免在通用整数寄存器中实现整数。FP 常量通常只是从中加载
.rodata
。尽管自 GCC 12 或 13 左右以来,GCC 更倾向于使用
mov
-immediate 而不是 GPR 和vmovd
plusvpbroadcastd
来处理具有重复元素的向量常量,而不是从静态存储中加载它们。但对于x
asm 语句的输入,即使是较旧的 GCC 也希望在 GPR 中使用它。这样,编译器会为 挑选 XMM0
y
(因此也为 挑选x
),并movd
自己执行 来获取那里的值。较旧的 GCC(如 GCC6)会将 mov-immediate 移动到 GPR,然后存储/重新加载到堆栈空间,默认为-mtune
,仅使用movd reg, xmm
或-mtune=intel
特定的 Intel(如 )-mtune=haswell
。"=x"
出于某种原因,使用 而不是"=rx"
会使 GCC6 执行movd .LC1(%rip), %xmm0
,即使它%xmm0
在两种情况下仍为操作数挑选。再次强调,
.space 0
这只是一个零字节 nop,它只是为了让我不必取消选中 Godbolt 编译器资源管理器上的“过滤注释”。我可以使用任何 GAS 语句.Ldummy=42 # comment
来定义符号,或者.global main
我本可以使用空字符串作为模板来编写
asm("" : "=rx"(y) : "0"(x));
。至少就正确性而言,编译器选择哪个寄存器(整数或 XMM)并不重要,因为"0"
这可以保证输入将选择与输出操作数 0 相同的寄存器。(https://gcc.gnu.org/onlinedocs/gcc/Simple-Constraints.html)如果您想将寄存器名称硬编码到 asm 模板中,则可以使用
register float y asm("xmm0");
强制"=r"(y)
选择 XMM0,但通常最好让编译器进行寄存器分配。在这种情况下,当您将其传递给 printf 时,x86-64 System V 调用约定确实希望它在 XMM0 中。Windows x64 则希望它在 XMM1 和 EDX 中。但它无论如何都需要一个
cvtss2sd
,因为可变函数会将其浮点参数提升为,double
因此浮点位于哪个寄存器并不重要,除了通过写入它读取的相同寄存器来避免错误依赖(出于性能原因)。GCC 在这里很愚蠢,将 XMM0 像素或归零;如果它已经完成了movd %eax, %xmm0
;cvtss2sd %xmm0, %xmm0
,那么它将对已经是输入依赖的同一寄存器具有输出依赖性,从而使其不再是问题。另请参阅https://stackoverflow.com/tags/inline-assembly/info以获取更多指南和有用的问答。
现在的手册也相当不错:https://gcc.gnu.org/onlinedocs/gcc/Using-Assembly-Language-with-C.html
这是不正确的,原因如下:
y
的xmm0
并不一定是正确的。%0
而不是%1
进行输入。r
的限制y
,但r
用于通用寄存器,而x
应该用于xmm
寄存器。修正后的代码为:
请注意,这假设您使用的是 x86-64,并且
vmovd
需要 AVX 支持(movd
否则使用)。