如何在没有移位/旋转的情况下在 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
清除,则该位被清除。
我正在为 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
清除,则该位被清除。