Mat*_*lia 5 c x86 assembly gcc x87
在我们的代码库中,我发现这个代码片段用于在x87上进行快速,向负无限1舍入:
inline int my_int(double x)
{
int r;
#ifdef _GCC_
asm ("fldl %1\n"
"fistpl %0\n"
:"=m"(r)
:"m"(x));
#else
// ...
#endif
return r;
}
Run Code Online (Sandbox Code Playgroud)
我不是非常熟悉GCC扩展汇编语法,但是从我从文档中收集到的内容:
r 必须是一个记忆位置,我在写回东西;x 必须是一个内存位置,数据来自哪里.现在,来回答我的问题:最终FPU堆栈是平衡的,但是如果所有8个位置都已经在使用并且我已经溢出呢?编译器如何知道它不能信任ST(7)它离开它的位置?应该添加一些clobber吗?
编辑我试图st(7)在clobber列表中指定它似乎影响codegen,现在我将等待对此事实的一些确认.
作为旁注:看看lrintglibc和MinGW 中的准系统的实现,我看到了类似的东西
__asm__ __volatile__ ("fistpl %0"
: "=m" (retval)
: "t" (x)
: "st");
Run Code Online (Sandbox Code Playgroud)
我们要求输入直接放在哪里ST(0)(避免这种情况无用fldl); 什么是"st"clobber?文档似乎只提到t(即堆栈的顶部).
查看
lrintglibc 和 MinGW 中准系统的实现,我看到类似Run Code Online (Sandbox Code Playgroud)__asm__ __volatile__ ("fistpl %0" : "=m" (retval) : "t" (x) : "st");我们要求将输入直接放置在
ST(0)其中(这避免了可能无用的情况fldl)
这实际上是将您想要的代码表示为内联汇编的正确方法。
为了生成尽可能最佳的代码,您需要利用输入和输出。与其硬编码必要的加载/存储指令,不如让编译器生成它们。这不仅引入了消除潜在不必要指令的可能性,还意味着编译器可以在需要时更好地调度这些指令(也就是说,它可以在先前的代码序列中交错指令,通常会最小化其成本)。
那个
"st"破坏者是什么?文档似乎只提到t(即堆栈的顶部)。
所述"st"撞指st(0)寄存器,即,所述的x87 FPU堆栈的顶部。Intel/MASM 符号所称的st(0),AT&T/GAS 符号通常简称为st. 而且,根据 GCC 的clobbers文档,clobbers列表中的项目是“寄存器名称或特殊 clobbers”("cc"(条件代码/标志)和"memory")。所以这只是意味着内联汇编破坏(覆盖)st(0)寄存器。之所以需要这个破坏器,是因为该fistpl指令弹出栈顶,从而破坏了 的原始内容st(0)。
关于此代码,我唯一担心的是文档中的以下段落:
Clobber 描述不得以任何方式与输入或输出操作数重叠。例如,当在 clobber 列表中列出该寄存器时,您可能没有描述具有一个成员的寄存器类的操作数。声明存在于特定寄存器中的变量(请参阅显式寄存器变量)并用作 asm 输入或输出操作数必须在 clobber 描述中没有提到的部分。特别是,没有办法指定输入操作数被修改而不将它们指定为输出操作数。
当编译器选择使用哪些寄存器来表示输入和输出操作数时,它不会使用任何被破坏的寄存器。因此,损坏的寄存器可用于汇编代码中的任何用途。
如您所知,t 约束意味着 x87 FPU 堆栈的顶部。问题是,这与st寄存器相同,并且文档非常清楚地说明我们不能有一个将相同寄存器指定为输入/输出操作数之一的clobber。此外,由于文档规定编译器被禁止使用任何被破坏的寄存器来表示输入/输出操作数,所以这个内联汇编提出了一个不可能的请求——在 x87 FPU 堆栈的顶部加载这个值而不把它放进去st!
现在,我假设 glibc 的作者知道他们在做什么,并且比你我更熟悉编译器的内联汇编实现,所以这段代码可能是合法的。
实际上,x87 的类似堆栈的寄存器的异常情况似乎迫使clobber 和操作数之间的正常交互出现异常。在官方文件说:
在 x86 目标上,在 asm 的操作数中使用类似堆栈的寄存器有几条规则。这些规则仅适用于类似堆栈的寄存器的操作数:
给定一组在 asm 中失效的输入寄存器,有必要知道哪些是由 asm 隐式弹出的,哪些必须由 GCC 显式弹出。
由 asm 隐式弹出的输入寄存器必须被显式破坏,除非它被约束为匹配输出操作数。
这完全符合我们的情况。
官方文档(链接部分底部)中出现的示例提供了进一步确认:
这个 asm 接受两个输入,由
fyl2xp1操作码弹出,并用一个输出替换它们。st(1)编译器需要使用clobber 才能知道fyl2xp1弹出两个输入。Run Code Online (Sandbox Code Playgroud)asm ("fyl2xp1" : "=t" (result) : "0" (x), "u" (y) : "st(1)");
在这里,clobberst(1)与 input constraint 相同u,这似乎违反了上面引用的关于 clobbers 的文档,但使用和证明的原因与"st"原始代码中用作 clobber 的原因完全相同,因为fistpl弹出输入。
说了这么多,既然您知道如何正确地用内联汇编编写代码,我必须回应以前的评论者,他们建议最好的解决方案是根本不使用内联汇编。只需调用lrint,它不仅具有您想要的确切语义,而且在某些情况下还可以由编译器更好地优化(例如,cvtsd2si当目标体系结构支持 SSE 时将其转换为单个指令)。