0

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

OJBECTIVE : 将 DF 原样返回给调用者。

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哪个实际上是主体的一部分,这个解决目标的序言/尾声有 18 个字节。


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

4

1 回答 1

3

您有一个不错的选择

  • 需要在函数进入/退出时清除 DF(私有辅助函数除外,它可以使用自定义调用约定)。想要使用的功能可以在任何或他们之前std简单地使用。cldcallret

    使用其他标志作为返回值的一部分是微不足道的:cld并且std不影响它们。

    在极少数情况下,您需要在按降序遍历的循环内进行函数调用,可能根本不使用lods或其他字符串指令。它们不是魔术,偶尔dec si/mov al, [si]或其他任何事情都不是代码大小的灾难。或者它意味着一个cldandstd在循环内,每个只有 1 个字节。

    大多数情况下,您希望清除 DF 以便向上循环,在这种情况下,您可以在循环内进行函数调用而不会出现任何问题。(并非所有时候,但这种设计最适合常见情况,处理不常见情况也不会太痛苦)。

一个相当不错的选择

  • 将所有标志(包括 DF)调用破坏(如果需要,可以用作返回值的一部分)。每个字符串 op 都需要一个cldorstd在同一个函数中。所以这真的不是一个很好的选择。

还有一个平庸的选择

  • 您当前的调用约定,其中 DF 保留调用并且在 function entry 具有未知值。每个使用字符串指令的函数都需要一个cldorstd因为它不能假设任何东西,以及一个 FLAGS 的保存/恢复。(除非您根据已知的调用者和他们将 DF 放入的状态进行优化)。

唯一具有优势的情况是在包含也需要设置 DF 的函数调用的循环中。

当您想在 FLAGS 的条件代码中返回状态以及保存/恢复 DF 时,只需将在 FLAGS 中设置条件代码的指令放在恢复调用者的 DF之后popf

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

在极少数情况下,您无法轻松地将最后一个字符串指令移动到最后一个标志设置指令之前,模拟字符串指令可能会更小。而不是 inc 或 dec,用 . 保持标志不变lea si, [si +- 1]。(SI 和 DI 在 16 位寻址模式下均有效,因此lea可用于它们。)

lods//没有一个是神奇的stosmovs甚至它们的rep版本也没有。如果您不关心性能(仅代码大小),您甚至可以rep movs在不接触标志的情况下进行仿真,使用慢速loop指令和备用寄存器(如果需要,使用push/保存/恢复一个)pop


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

您的代码示例不会恢复调用者的 DF,它会无条件地清除它。它相当于cld.

要恢复调用者的 DF,您需要从 first 中提取 DF 位pushf,将其合并到pushf函数末尾的 FLAGS 值中,然后 popf. 这显然是可能的,但比您显示的效率低得多(并且代码大小更大)。


另请注意,这popf很慢:在 Haswell 上它是 9 微指令,并且每 18 个周期有一个吞吐量。如果您只关心代码大小而不是性能,那么需要pushf/popf的设计不一定是坏的,但在我看来,在进入/退出时要求 DF 清除将在大多数情况下赢得代码大小以及性能。

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

于 2018-02-24T06:57:30.530 回答