X86 获取比较指令的值
据我所知,cmp指令将设置标志寄存器中的一些位。然后,您可以使用诸如jle、jnp等指令来基于这些指令进行分支 我想知道的是如何从比较中恢复整数值 示例:以下是有效的c语法X86 获取比较指令的值,x86,compare,X86,Compare,据我所知,cmp指令将设置标志寄存器中的一些位。然后,您可以使用诸如jle、jnp等指令来基于这些指令进行分支 我想知道的是如何从比较中恢复整数值 示例:以下是有效的c语法 y = x[a >= 13]; 因此,将a与13进行比较,得到分别解释为1或0的真或假。但是,1或0必须作为整数馈送到数组访问中。编译器将做什么 我能想到的是: 进行比较,然后分支到x[0]或x[1] 进行比较,然后分支以执行tmp=0或tmp=1,然后执行x[tmp] 可能对标志执行一些奇特的逻辑(不确定是否有直接
y = x[a >= 13];
因此,将a与13进行比较,得到分别解释为1或0的真或假。但是,1或0必须作为整数馈送到数组访问中。编译器将做什么
我能想到的是:
进行比较,然后分支到x[0]或x[1]
进行比较,然后分支以执行tmp=0或tmp=1,然后执行x[tmp]
可能对标志执行一些奇特的逻辑(不确定是否有直接访问标志的指令)
我试图看看gcc在这个代码示例中给出了什么,但是不可能从它抛出的所有额外垃圾中找出逻辑
我正在编写一个编译器,因此任何建议都将不胜感激。基本上有三种方法可以做到这一点。我一次看一个 其中一种方法基本上就是您在问题中描述的:进行比较,然后分支到分别实现这两种可能性的代码。例如:
cmp [a], 13 ; compare 'a' to 13, setting flags like subtraction
jge GreaterThanOrEqual ; jump if 'a' >= 13, otherwise fall through
mov eax, [x * 0 * sizeof(x[0])] ; EAX = x[0]
jmp Next ; EAX now loaded with value, so do unconditional jump
GreaterThanOrEqual:
mov eax, [x * 1 * sizeof(x[0])] ; EAX = x[1]
; EAX now loaded with value; fall through
Next:
mov [y], eax ; store value of EAX in 'y'
xor edx, edx
cmp [a], 13
setge dl
dec edx
and dl, 123
add edx, 125
; do whatever with EDX
mov edx, -8 ; EDX = -8
mov eax, 125 ; EAX = 125
cmp [a], 13 ; compare 'a' to 13 and set flags
cmovge edx, eax ; EDX = (a >= 13 ? EAX : EDX)
; do whatever with EDX
int Foo(int a)
{
return a >= 13;
}
通常,编译器会尝试在寄存器中保留更多的值,但这会让您了解基本逻辑。它进行比较,或者分支到读取/加载x[1]
的指令,或者分支到读取/加载x[0]
的指令。然后,它移到一条指令上,该指令将该值存储到y
中
您应该能够看到,这是相对低效的,因为需要所有的分支。因此,优化编译器不会生成这样的代码,特别是在具有基本三元表达式的简单情况下:
(a >= 13) ? 1 : 0
甚至:
(a >= 13) ? 125 : -8
有一些位旋转技巧可以用来进行这种比较并获得相应的整数,而无需执行分支
这就引出了第二种方法,即使用SETcc
指令。cc
部分代表“条件代码”,所有条件代码与条件跳转指令的条件代码相同。(事实上,您可以将所有条件跳转指令编写为Jcc
)例如,jge
表示“大于或等于时跳转”;类似地,setge
表示“大于或等于时设置”。简单
SETcc
的诀窍在于它设置一个字节大小的寄存器,这基本上意味着AL
、CL
、DL
或BL
(有更多选项;您可以设置其中一个寄存器的高字节,和/或在64位长模式下有更多选项,但这些是操作数的基本选择)
下面是实现此策略的代码示例:
xor edx, edx ; clear EDX
cmp [a], 13 ; compare 'a' to 13, setting flags like subtraction
setge dl ; set DL to 1 if greater-than-or-equal, or 0 otherwise
mov eax, [x * edx * sizeof(x[0])]
mov [y], eax
很酷,对吧?分支机构被淘汰。所需的0或1直接加载到DL
,然后作为加载的一部分(MOV
指令)
这里唯一稍微令人困惑的是,您需要知道DL
是完整32位EDX
寄存器的低位字节。这就是为什么我们需要预先清除完整的EDX
,因为setge dl
只影响低字节,但我们希望完整的EDX
为0或1。事实证明,对完整寄存器进行预调零是可行的,但还有其他方法,比如在SETcc
指令之后使用MOVZX
。链接的答案非常详细,所以我在这里不赘述。关键是SETcc
只设置寄存器的低位字节,但后续指令要求整个32位寄存器都有该值,因此需要消除高位字节中的垃圾
无论如何,当您编写类似于y=x[a>=13]
的代码时,编译器99%的时间都会生成这些代码。SETcc
指令提供了一种根据一个或多个标志的状态设置字节的方法,就像您可以在标志上进行分支一样。这基本上就是您所想的允许直接访问标志的指令
这实现了
(a >= 13) ? 1 : 0
但是如果你想做呢
(a >= 13) ? 125 : -8
就像我之前提到的?好的,您仍然使用SETcc
指令,但之后您会进行一些奇妙的位旋转,将结果0或1“修正”为您实际需要的值。例如:
cmp [a], 13 ; compare 'a' to 13, setting flags like subtraction
jge GreaterThanOrEqual ; jump if 'a' >= 13, otherwise fall through
mov eax, [x * 0 * sizeof(x[0])] ; EAX = x[0]
jmp Next ; EAX now loaded with value, so do unconditional jump
GreaterThanOrEqual:
mov eax, [x * 1 * sizeof(x[0])] ; EAX = x[1]
; EAX now loaded with value; fall through
Next:
mov [y], eax ; store value of EAX in 'y'
xor edx, edx
cmp [a], 13
setge dl
dec edx
and dl, 123
add edx, 125
; do whatever with EDX
mov edx, -8 ; EDX = -8
mov eax, 125 ; EAX = 125
cmp [a], 13 ; compare 'a' to 13 and set flags
cmovge edx, eax ; EDX = (a >= 13 ? EAX : EDX)
; do whatever with EDX
int Foo(int a)
{
return a >= 13;
}
这适用于几乎任何二进制选择(两个可能的值,取决于条件),优化编译器足够聪明,可以解决这一问题。无分支代码;很酷
有第三种方法可以实现这一点,但它在概念上与我们刚才讨论的第二种方法非常相似。它使用条件移动指令,这只是基于标志状态进行无分支设置的另一种方式。条件移动指令是CMOVcc
,其中cc
再次引用“条件代码”,与前面的示例完全相同。CMOVcc
指令是在Pentium Pro中引入的,大约在1995年,并且从那时起就已经出现在所有处理器中(当然,不是Pentium MMX,而是Pentium II和更高版本),因此基本上就是您今天所看到的一切
代码非常相似,只是顾名思义,这是一个有条件的移动,所以需要更多的初步设置。具体而言,您需要将候选值加载到寄存器中,以便选择正确的值:
xor edx, edx ; EDX = 0
mov eax, 1 ; EAX = 1
cmp [a], 13 ; compare 'a' to 13 and set flags
cmovge edx, eax ; EDX = (a >= 13 ? EAX : EDX)
mov eax, [x * edx * sizeof(x[0])]
mov [y], eax
请注意,将EAX
移动到EDX
是有条件的,仅当标志指示条件ge
(大于或等于)时才会发生。所以,英国航空管理局(ba)是这样认为的