有没有更好的方法在函数或子程序中保留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
的函数可以在它们创建的任何 call
或 ret
之前简单地使用 cld
。
使用其他标志作为 return 值的一部分是微不足道的:cld
和 std
不会影响它们。
在极少数情况下,您需要在按降序遍历的循环中进行函数调用,也许根本不要使用 lods
或其他字符串指令。它们不是魔术,偶尔 dec si
/ mov al, [si]
或其他对 code-size 来说不是灾难的东西。或者表示循环内部有一个cld
和std
,每个只有1个字节。
大多数时候你希望 DF 清除所以你向上循环,在这种情况下你可以在循环内进行函数调用而没有任何问题。 (不是 all 的时间,但这种设计对于常见的情况是最好的,并且可以轻松处理不常见的情况)。
一个比较好的选择:
- 拥有所有标志(包括 DF)call-clobbered(如果需要,可用作 return 值的一部分)。每个字符串操作都需要在同一函数内使用
cld
或 std
。所以这不是一个很好的选择。
还有一个平庸的选项:
- 您当前的调用约定,其中 DF 是 call-preserved 并且在函数入口 上具有未知值。每个使用字符串指令的函数都需要一个
cld
或 std
因为它不能假设任何东西, 和 一个 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 位代码中也不能很好地工作。
我喜欢涉足优化,主要是在 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
的函数可以在它们创建的任何call
或ret
之前简单地使用cld
。使用其他标志作为 return 值的一部分是微不足道的:
cld
和std
不会影响它们。在极少数情况下,您需要在按降序遍历的循环中进行函数调用,也许根本不要使用
lods
或其他字符串指令。它们不是魔术,偶尔dec si
/mov al, [si]
或其他对 code-size 来说不是灾难的东西。或者表示循环内部有一个cld
和std
,每个只有1个字节。大多数时候你希望 DF 清除所以你向上循环,在这种情况下你可以在循环内进行函数调用而没有任何问题。 (不是 all 的时间,但这种设计对于常见的情况是最好的,并且可以轻松处理不常见的情况)。
一个比较好的选择:
- 拥有所有标志(包括 DF)call-clobbered(如果需要,可用作 return 值的一部分)。每个字符串操作都需要在同一函数内使用
cld
或std
。所以这不是一个很好的选择。
还有一个平庸的选项:
- 您当前的调用约定,其中 DF 是 call-preserved 并且在函数入口 上具有未知值。每个使用字符串指令的函数都需要一个
cld
或std
因为它不能假设任何东西, 和 一个 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 位代码中也不能很好地工作。