如何在没有移位/旋转的情况下在 GPR 的特定位中设置进位标志?

How to set carry flag in specific bit in GPR without shifts / rotations?

我正在为 Intel 80386 处理器编写 NASM 程序,我需要在 GPR(通用寄存器)的特定位中设置进位标志的值,而不更改寄存器中的其他位。

有没有可能不使用任何类型的 shifts/rotations 就可以这样做?

一种无分支的方法是用进位标志填充临时寄存器,掩码你想要的位,然后用你的目标寄存器或它。

使用 EAX 作为 scratch(将其视为正在处理的 32 位值):

sbb eax, eax
and eax, 1 << 16  ; Adjust bitshift in constant for the desired bit. Multiple bits can be used.

如果未设置进位标志,sbb 将执行 eax = eax - eax = 0。如果设置了进位标志,sbb 将执行 eax = eax - (eax + 1) = -1,因此所有位都已设置。所需的位 is/are 然后被屏蔽。

之后,您需要在目标中设置适当的位。如果钻头处于初始已知状态,则可以简化此操作。使用 EBX 作为目标:

and ebx, ~(1 << 16)  ; Same value at before. This doesn't need to be a shifted bit, it could be a number.
or  ebx, eax

根据暂存寄存器之前发生的情况(此处为 EAX),可能值得查看一些优化信息,例如 https://www.agner.org/optimize/。有些处理器会认识到 EAX 的新值不依赖于旧值,有些会认为它对旧值有(错误的)依赖性。

看了之后,文档“Optimizing subroutines in assembly language”在“Replacing conditional jumps with bit-manipulation instructions”一节中提到了上述sbb技巧。

使用EAX(累加器)作为临时寄存器将导致更小的代码大小。

可预见的案例

如果值 CF 假设是高度可预测的,使用条件跳转和代码如下:

    ... operation that sets CF ...
    jnc   nc           ; skip setting bit if CF is clear
    or    eax, 1       ; set bit in eax
    jmp   end
nc: and   eax, ~1      ; clear CF in eax
end:

不可预测的案例

如果需要无分支代码(例如,因为 CF 的值很难预测,或者如果这是一个加密应用程序),请考虑使用条件移动。

计算 CF 处于关键路径

假设我们想在执行一些操作后将 eax 中的最低有效位设置为 CF 的值:

    mov    ecx, eax    ; make a copy of eax
    or     ecx, 1      ; set CF in the copy
    and    eax, ~1     ; clear CF in the original
    ... operation that sets CF ...
    cmovc  eax, ecx    ; set eax to ecx if CF was set

此代码比中的指令多,但其关键路径延迟更短,假设计算CF在关键路径上但计算eax的先验值不是。它是否真的更好取决于具体情况。

然而,简单的变体

    and     eax, ~1    ; clear CF in eax
    ... operation that sets CF ...
    adc     eax, 0     ; add carry flag to eax

可能最适合这种特定情况(将最低有效位设置为 CF),因为它避免了两个微操作,同时具有相同的关键路径延迟。

计算 EAX 处于关键路径

如果另一方面计算 eax 在关键路径上但计算 CF 不是,Thomas Jager 的解决方案很好,但需要调整以清除最低有效位eax 之前,因此如果 CF 清除,则该位被清除。