有没有更好的方法在函数或子程序中保留DF

Is there a better method to preserve DF in function or subroutine

我喜欢涉足优化,主要是在 space 的背景下,通过考虑算法流程。在尝试了几种不同的场景之后,这似乎是我能想到的最好的场景。同样假设,这是必须的,因为调用此过程时,有时会设置 DF,有时不会。

OJBECTIVE: Return DF to caller unaltered.

00  55       push   bp
01  89E5     mov    bp, sp
03  9C       pushfw

04  FD       std

    ... Function/Subroutine body

05  58       pop    ax          ; Retrieve original flags
06  F6C404   test   ah, DF      ; Was DF already set
09  7506     jnz    Exit        ; If so, nothing to do

; Reset DF without altering state of any other flags

0B  9C       pushfw
0C  8066FFFB and byte [bp-1], 0FBH  ; Strip DF from MSB of EFLAGS
10  9D       popfw

       Exit:
11  C9       leave
12  C3       ret

不包括 STD,它实际上是主体的一部分,这个 prologue/epilogue 地址 objective 有 18 个字节。


有没有人实施过比这更严格的方法?

你有一个不错的选择:

  • 需要在函数入口/出口清除 DF(私有辅助函数除外,它可以使用自定义调用约定)。想要使用 std 的函数可以在它们创建的任何 callret 之前简单地使用 cld

    使用其他标志作为 return 值的一部分是微不足道的:cldstd 不会影响它们。

    在极少数情况下,您需要在按降序遍历的循环中进行函数调用,也许根本不要使用 lods 或其他字符串指令。它们不是魔术,偶尔 dec si / mov al, [si] 或其他对 code-size 来说不是灾难的东西。或者表示循环内部有一个cldstd,每个只有1个字节。

    大多数时候你希望 DF 清除所以你向上循环,在这种情况下你可以在循环内进行函数调用而没有任何问题。 (不是 all 的时间,但这种设计对于常见的情况是最好的,并且可以轻松处理不常见的情况)。

一个比较好的选择:

  • 拥有所有标志(包括 DF)call-clobbered(如果需要,可用作 return 值的一部分)。每个字符串操作都需要在同一函数内使用 cldstd。所以这不是一个很好的选择。

还有一个平庸的选项:

  • 您当前的调用约定,其中 DF 是 call-preserved 并且在函数入口 上具有未知值。每个使用字符串指令的函数都需要一个 cldstd 因为它不能假设任何东西, 一个 save/restore FLAGS。 (除非你根据已知的调用者和他们将 DF 放入的状态进行优化)。

它唯一有优势的地方是在一个包含也需要 DF 设置的函数调用的循环中。

当你想returnFLAGS和save/restoreDF的条件码中的状态时,只需将设置condition-codes的指令放在FLAGS中即可after恢复调用者的DFpopf.

在条件代码中没有有趣状态的函数中,只需使用 popf 即可恢复所有调用者 FLAGS。在 FLAGS 中 return 没有任何有趣内容的函数中,您不需要将调用者的 DF 合并到当前函数的 FLAGS 中。

在极少数情况下,您无法轻易地将最后一个字符串指令移动到最后一个 flag-setting 指令 之前,模拟字符串指令可能会更小。不用 inc 或 dec,使用 lea si, [si +- 1] 保持标志不变。 (SI 和 DI 在 16 位寻址模式下都有效,所以 lea 可以用在它们上面。)

lods / stos / movs

None 是神奇的,甚至是它们的 rep 版本。如果您不关心性能(仅 code-size),您甚至可以在不触及标志的情况下模拟 rep movs,使用 the slow loop instruction 和一个备用寄存器(save/restore 一个 push/pop 如果需要)


 0B  9C       pushfw
 0C  8066FFFB and byte [bp-1], 0FBH  ; Strip DF from MSB of EFLAGS
 10  9D       popfw

你的code-sample不会恢复来电者的DF,它会无条件清除它。相当于cld.

要恢复调用者的 DF,您需要从第一个 pushf 中提取 DF 位,将其合并到函数末尾 pushf 的 FLAGS 值中,然后然后 popf。这显然是可能的,但比你展示的要低效得多(并且在 code-size 中更大)。


还要注意 popf 很慢:在 Haswell 上它是 9 微指令,并且每 18 个周期有一个吞吐量。如果你只关心 code-size,而不关心性能,那么需要 pushf/popf 的设计不一定不好,但在我看来,要求 entry/exit 上的 DF clear 会大多数情况下,代码大小和性能都是赢家。

这是 32 位和 64 位调用约定选择用于处理 DF 的方式,我不明白为什么它在 16 位代码中也不能很好地工作。