【问题标题】:Assembly 64bit: How to return a double value?汇编 64 位:如何返回双精度值?
【发布时间】:2016-06-09 15:40:51
【问题描述】:

如何在汇编程序中返回 64 位值? 我试过这个:

C 程序:

#include <stdio.h>

double result=0;
double a = 10;
extern double func(double a);

 int main() {       
    result = func(a);
    printf("result: %f\n", result);     
    return 0;
   }

组装:

      section .bss
      x: resq 1

      section .text 

      global func

      func:

      movq qword[x],xmm0
      fld qword [x]
      fld qword [x]
      fadd
      movq xmm0,qword[x]

      ret

它应该返回 20.0 但它始终是 10.0 我做错了什么?

【问题讨论】:

  • 您永远不会存储结果。顺便说一句,使用 SSE 而不是 x87 可能更简单。
  • Elderbug 是正确的。 fadd 添加了 st(0) 和 st(1),将值存储在 st(0) 中。您不会使用堆栈顶部的值更新 [x] 中的值。您也不要在返回之前从 FPU 堆栈中弹出额外的值(如果您调用此函数 5 次,这将导致 FPU 堆栈溢出问题)。正如 Elder 指出的那样,除非给定的任务要求您这样做,否则您可以使用 SSE 而不是 FPU。
  • 代码也可以简化,但我很好奇你是编译32位还是64位代码?我从你得到的结果中假设,你的可执行文件是 64 位的。
  • 如果您可以使用 SSE,您可以使用 ADDSD 指令,例如 section .text; global func; func: addsd xmm0, xmm0; ret 。这会将 xmm0 中的双精度标量添加到 xmm0 中,并将结果存储在 xmm0 中。
  • @PeterCordes :是的,我的主要观点是一种间接的方式来暗示它总是有助于告诉我们问题中的目标环境。不太可能,但可以通过使用 -fno-leading-underscore 之类的东西来避免 Win32 的 GCC 上的下划线。我非常怀疑他是,但一切皆有可能。

标签: gcc assembly nasm


【解决方案1】:

您不能混淆 FPU 和 XMM 计算。 当您在 FPU 上计算某些内容时,您必须将其存储(如@Elderbug 所说)在内存中,然后您必须将其加载到 XMM 寄存器以在 Win OS 上的 x64 上的 64 位 proc 上返回它。 在 64 位系统上使用 FPU 仍然有一个优势,因为 FPU 的内部精度可以是 80 位(如果您使用正确的 FPU 控制字:位 8,9 float32(24 位尾数)= 00b 双浮点(53 位尾数)= 10b 扩展精度(64位尾数)= 11b

如果你想使用 FPU:

fld QWORD PTR x   ; laod var to FPU: into ST(0)  (MASM Syntax)
fadd ST(0), ST(0)   ; this adds [x]+[x]
fstp QWORD PTR x  ; store result back in var
movsd xmm0, QWORD PTR x

注意:对于 movsd,始终需要 SSE2。 (在 SSE1 机器上会发生 GP 故障!请参阅 Intel® 64 和 IA-32 架构软件开发人员手册: http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html 但是,如果您运行的 Windows8/8.1/10 对您来说从来都不是问题,则会导致操作系统本身请求 SSE2 作为系统要求。

编辑:SSE2 是 x86-x64 中的基线(正如 Peter Cordes 在 cmets 中所述),因此您可以始终在 64 位上使用它。

如果您想将 SIMD 与 XMM 寄存器一起使用:

movsd xmm0, QWORD PTR x
addsd xmm0, xmm0   ; this instruction also requires SSE2   
; ok, retun xmm0

另外请注意,您也不能混淆 XMM 和 MMX-Registers! (指令 MOVQ2DQ 和 MOVDQ2Q 可以将它们从一种转换为另一种,但其他指令不能)

如果您的函数使用参数并且应该在 Windows 操作系统上运行,则需要确保函数 prolog/epilog 有效。见:https://future2048.blogspot.com

【讨论】:

  • x86-64 以 SSE2 作为基线。只有在 32 位代码中执行此操作时,您的 SSE1 警告才适用。 (并且您可能不应该在可能在没有 SSE2 的机器上运行的代码中使用在 xmm regs 中返回双精度值的调用约定。但如果您确实只想使用 SSE1,您仍然可以使用 movlps,但不要因为这将与寄存器的旧内容合并,而不是将高 64 位归零。(对 xmm 寄存器旧值的错误依赖))
  • 另外,关于:x87 精度控制的初始设置:参见this one of Bruce Dawson's excellent series of articles about FP。 VC++ 的 CRT 代码将 x87 单元设置为 53 位尾数精度,至少对于 32 位可执行文件。我没有重新阅读整篇文章,看看它什么时候不理会它。而且我想我已经阅读了有关 directx 更改 x87 精度的内容。很高兴我不使用 Windows。 :)
【解决方案2】:

@Michael Petch 指出,使用以下代码,整个函数可以更加更有效率:

addsd xmm0, xmm0   ; Add input parameter to itself
ret                ; Done!  (return values go in xmm0)

x86-64 在 XMM 寄存器中传递/返回 double,而不是在内存或 x87 堆栈中。(适用于 x86-64 System V ABI/调用约定和 Windows x64。请参阅链接在the x86 tag wiki)


发布的代码没有 cmets。评论它会帮助 OP,所以...

;; Buggy original version with comments
movq qword[x],xmm0  ; Store current value in memory  [Why?]
fld qword [x]       ; Load current value from memory [Why??]
fld qword [x]       ; Load current value from memory again
fadd                ; Add top two stack items

movq xmm0,qword[x]  ; reload original value from memory, unmodified

@ElderBug 注意到 OP 在执行最终的 movq 之前忘记将 fadd 的结果存储到内存中,所以这个函数只是返回它的输入,就像
double foo(double x) { return x; } 但在 x87 上留下垃圾堆栈。


@Michael Petch 继续指出,原始代码在浮点堆栈上留下了大量“碎片”——没有尝试使用各种 pop 版本的指令(fstp,或faddp 而不是fadd)。这为下一个浮点函数留下了更少的空间 - 直到最终导致浮点堆栈溢出,导致意外的 NaN!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多