我正在为英特尔 80386 处理器编写 NASM 中的程序,我需要在 GPR(通用寄存器)的特定位中设置进位标志的值,而不更改寄存器中的其他位。
是否有可能在不使用任何形式的轮班/轮换的情况下这样做?
我正在为英特尔 80386 处理器编写 NASM 中的程序,我需要在 GPR(通用寄存器)的特定位中设置进位标志的值,而不更改寄存器中的其他位。
是否有可能在不使用任何形式的轮班/轮换的情况下这样做?
这样做的一种无分支方法是用进位标志填充临时寄存器,掩码你想要的位,然后用你的目标寄存器或它。
用作从头开始(将EAX
其视为被操作的 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
,因此所有位都已设置。然后掩蔽所需的位。
之后,您需要在目标中设置适当的位。如果该位处于初始已知状态,这可以简化。用作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》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
难以预测,或者如果这是一个加密应用程序),请考虑使用条件移动。
假设我们想在执行一些操作后将最低有效位设置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
这段代码比Thomas Jager 的答案中的指令更多,但它的关键路径延迟更短,假设计算CF
在关键路径上但计算的先验值eax
不在。它是否实际上更好取决于环境。
尽管如此,简单的变体
and eax, ~1 ; clear CF in eax
... operation that sets CF ...
adc eax, 0 ; add carry flag to eax
可能最适合这种特定情况(将最低有效位设置为CF
),因为它避免了两个微操作,同时具有相同的关键路径延迟。
另一方面,如果计算eax
在关键路径上但计算CF
不在关键路径上,Thomas Jager 的解决方案很好,但需要对其进行调整以eax
预先清除最低有效位,以便在清除时清除该位CF
。