x86汇编语言:浮点数比较

16
作为编译器项目的一部分,我需要编写用于x86的GNU汇编器代码来比较浮点数值。我试图找到有关如何执行此操作的在线资源,并且从我的理解中,它的工作方式如下所示:
假设我要比较的两个值是浮点栈上唯一的值,则fcomi指令将比较这些值,并设置CPU标志,以便可以使用je、jne、jl等指令。
我提出问题是因为这种方法只有在某些情况下才有效。例如:
.section    .data
msg:    .ascii "Hallo\n\0"
f1:     .float 10.0
f2:     .float 9.0

.globl main
    .type   main, @function
main:
    flds f1
    flds f2
    fcomi
    jg leb
    pushl $msg
    call printf
    addl $4, %esp
leb:
    pushl $0
    call exit

即使我认为应该打印"Hallo",但程序并没有打印出来。如果你交换f1和f2的位置,它仍然不会打印,这是一个逻辑矛盾。然而,je和jne似乎工作正常。

我做错了什么?

PS:fcomip是弹出一个值还是两个值?

1个回答

42
TL:DR: 使用以上/以下条件(例如对于无符号整数)来测试比较结果。
由于各种历史原因(通过fcom/fstsw/sahf从FP状态字映射到FLAGS,新的PPro中的fcomi匹配),FP比较设置CF,而不是OF/SF。参见http://www.ray.masmcode.com/tutorial/fpuchap7.htm。请注意保留html标签。
现代 SSE/SSE2 标量比较使用 [u]comiss / sd] 将结果存入 FLAGS 中 也请参考此处。与 SIMD 比较不同,它们没有谓词作为指令的一部分,因为它们只对每个元素产生单个全零/全一的结果集,而不是一组 FLAGS。保留 html 标签。

这些内容来自于 Intel 64和IA-32体系结构软件开发手册的第2卷。

FCOMI只设置了一些CMP的标志。你的代码中有%st(0) == 9%st(1) == 10。(因为它们在栈上加载),参考第2A卷第3-348页的表格,你可以看到这种情况是"ST0 < ST(i)",所以它将清除ZF和PF并设置CF。同时在第2A卷第3-544页上,你可以读到JG的意思是"如果大于则跳转短(ZF=0且SF=OF)"。换句话说,它测试符号、溢出和零标志,但FCOMI不设置符号或溢出!

根据你想要跳转的条件,你应该查看可能的比较结果,并决定何时跳转。

+----------------------+---+---+---+
| 比较结果             | Z | P | C |
+----------------------+---+---+---+
| ST0 > ST(i)          | 0 | 0 | 0 |
| ST0 < ST(i)          | 0 | 0 | 1 |
| ST0 = ST(i)          | 1 | 0 | 0 |
| 无序                | 1 | 1 | 1 |  一个或两个操作数是NaN。
+----------------------+---+---+---+

我制作了这个小表格以便更容易理解:

+--------------+---+---+-----+------------------------------------+
| 测试         | Z | C | Jcc | 注释                               |
+--------------+---+---+-----+------------------------------------+
| ST0 < ST(i)  | X | 1 | JB  | 当CF = 1时,ZF永远不会被设置        |
| ST0 <= ST(i) | 1 | 1 | JBE | ZF或CF都可以                        |
| ST0 == ST(i) | 1 | X | JE  | 在这种情况下,CF永远不会被设置      |
| ST0 != ST(i) | 0 | X | JNE |                                    |
| ST0 >= ST(i) | X | 0 | JAE | 只要CF清除,我们就没问题            |
| ST0 > ST(i)  | 0 | 0 | JA  | CF和ZF都必须清除                    |
+--------------+---+---+-----+------------------------------------+
图例:X: 不关心, 0: 清除, 1: 设置
换句话说,条件码与使用无符号比较相匹配。如果您使用的是FMOVcc,则也是如此。
如果fcomi的操作数(一个或两个)为NaN,它会设置ZF = 1 PF = 1 CF = 1。(FP比较有4种可能结果:><==或无序)。如果您关心代码对NaN的行为,您可能需要额外的jpjnp。但并不总是需要:例如,ja仅在CF=0且ZF=0时为真,在无序情况下它将被视为未执行。如果您希望无序情况采用与以下相同的执行路径,则ja就足够了。

如果您想要打印(例如:if (!(f2 > f1)) { puts("hello"); }),则应使用JA,如果不想要打印,则应使用JBE(对应于if (!(f2 <= f1)) { puts("hello"); })。请注意,这可能会有点令人困惑,因为我们只在不跳转时才打印。


关于您的第二个问题:默认情况下,fcomi 不会弹出任何内容。 您需要它的近亲 fcomip,它会弹出 %st0。 使用后应始终清除 fpu 寄存器栈,因此总体而言,假设您想要打印消息,则您的程序最终如下所示:
.section    .rodata
msg:    .ascii "Hallo\n\0"
f1:     .float 10.0
f2:     .float 9.0 

.globl main
    .type   main, @function
main:
    flds   f1
    flds   f2
    fcomip
    fstp   %st(0) # to clear stack
    ja     leb # won't jump, jbe will
    pushl  $msg
    call   printf
    addl   $4, %esp
leb:
    pushl  $0
    call   exit

7
非常出色的回答。优秀。有一个小细节要提醒一下:ja的反义词是jbe而不是jb - Ray Toal
2
@Ray Toal:您说得完全正确。虽然在这种情况下不会产生任何影响,但我改变了示例,因为那样更有意义。 - user786653

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接