有没有更好的方法来保留功能或子程序中的DF

时间:2018-02-24 02:21:20

标签: assembly x86-16 eflags

我喜欢涉及优化,主要是在空间环境中考虑算法流程。在尝试了几种不同的场景之后,这个场景似乎是我能想到的最好的场景。假设这也是必须的,因为在调用此过程时,有时会设置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个字节为单位。

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

1 个答案:

答案 0 :(得分:3)

您有一个不错的选择

  • 要求DF清除函数入口/出口(私有帮助程序函数除外,它可以使用自定义调用约定)。想要使用std的功能可以在cldcall之前使用ret

    使用其他标记作为返回值的一部分非常简单:cldstd不会影响它们。

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

    大多数情况下,你希望DF清除,以便你向上循环,在这种情况下你可以在循环内部进行函数调用而不会出现任何问题。 (当时不是所有,但这种设计对于常见情况是最好的,并且可以毫不费力地处理不常见的情况)。

一个相当不错的选择

  • 让所有标志(包括DF)调用clobbered (并根据需要用作返回值的一部分)。每个字符串op在同一个函数中需要cldstd。所以这不是一个非常好的选择。

一个平庸的选项

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

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

如果要在FLAGS的条件代码中返回状态以及保存/恢复DF,只需将 {{1>之后设置条件代码的指令放在FLAGS 中恢复来电者的DF

在条件代码中没有有趣状态的函数中,只需使用popf即可恢复所有调用者FLAGS。你不需要将调用者的DF合并到当前函数的FLAGS中,而这些函数不会返回任何有趣的FLAGS。

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

lea / lods / stos都不是神奇的,甚至不是movs版本。如果您不关心性能(仅限代码大小),您可以使用the slow loop instruction和备用寄存器(使用rep保存/恢复)来模拟rep movs而不触及标记/ push如果需要)

pop

您的代码示例无法恢复来电者的DF,它会无条件地清除它。它相当于 0B 9C pushfw 0C 8066FFFB and byte [bp-1], 0FBH ; Strip DF from MSB of EFLAGS 10 9D popfw

要恢复来电者的DF,您需要从第一个cld中提取DF位,并将其合并到最后pushf的FLAGS值中。该函数,然后 pushf。这显然是可能的,但效率明显低于代码大小(并且代码大小更大)。

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

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