汇编:使用两个 fld、fcomp、fnstsw 和 test 41h 转换为 if 语句

Assembly: converting to if statement using two fld, fcomp, fnstsw and test 41h

有人可以帮助我理解以下代码吗?

fld qword ptr [L1000F168]
fcomp   qword ptr [L1000A2F0]
fld qword ptr [L1000F168]
fnstsw  ax
test    ah,41h
jnz L100012F0

它是编译器从执行代码转换为汇编的输出。

到现在为止我发现的是

if(value[L1000F168] != value[L1000A2F0])
   continue following
else
   goto L100012F0

我说得对吗?

我不明白为什么[L1000F168]加载了两次,为什么ah41h在这里比较?因为 41h 表示(无效操作)或(堆栈错误)?

有关 x87 状态字的信息,请参阅 this page

请注意,16 位状态字存储到ax,但test 指令只查看高8 位(ah)。所以 41h 匹配状态字的 C0C3 位。在 ah 上使用 8 位测试可避免因使用 16 位 testimm16 而降低 Intel CPU 速度。 (操作数大小前缀改变指令其余部分的长度,即所谓的长度改变前缀解码器停顿)。

fld / fcomp / fld / fnstsw:我也觉得这很奇怪。我想知道目标是否是根据内存位置设置状态字中的非规范位,但是 C0 等等基于 fcomp 设置。 (这不是您得到的,因为 fld 未定义或设置 C0-3。)

Intel 的 insn 参考手册说 fld 未定义 C0C3,因此生成此代码的编译器取决于某些特定行为。也许没有 fwait,状态字不会从 fcomp 更新?我还没有弄明白整个 fwait 的事情。我没有找到(或努力寻找)关于何时需要 fwait 在旧 CPU 上以及何时不需要的解释。据我了解,你永远不会在 P5 或更高版本上这样做。

无论如何,我认为这段代码的作用是:

if (! ([L1000F168] > [L1000A2F0]))
    goto L100012F0;

测试不大于与测试 <= 或无序相同。这假定 fld 实际上没有修改 C0C3。也许手册只说它没有定义它们,因为它可以在加载非正常或其他一些条件时设置它们?或者也许它在当前的硅中根本没有。或者这可能是英特尔手册中的另一个错误,而这实际上是好的。或者也许这段代码不再起作用了!最好用调试器测试一下。

我认为获得程序员可能希望的结果的更明智的方法是

fld    qword ptr [L1000F168]
fcom   qword ptr [L1000A2F0]
fnstsw ax
test   ah,41h     ; C0|C3: both zero iff st(0) > [L1000A2F0]
jnz    L100012F0

由于 OP 说代码是编译器输出,我想这意味着它没有很好的 x87 优化来避免 pop/reload 相同的值。

For P6 and later CPUs:

fld    qword ptr [L1000F168]
fcomi  qword ptr [L1000A2F0]
jna    L100012F0   ;  jump unless st(0) > [L1000A2F0]

我可能对其中一个比较有倒退的感觉,所以如果有什么不合理的地方,请仔细检查。

这段代码来自哪里?它是编译器输出吗?或者它可能是手写的 asm?

fcomp qword ptr [L1000A2F0] 将 stack[0] 与 L1000A2F0 中的值进行比较,并将其弹出到堆栈的末尾。

example
stack[0] = 50
[L1000A2F0] = 45

if (stack[0] > [L1000A2F0])
pop stack[0]; // (stack[7] = stack[0])
else
pop [L1000A2F0]; // (stack[7] = [L1000A2F0])

希望这对您有所帮助。