X86 获取比较指令的值

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] 可能对标志执行一些奇特的逻辑(不确定是否有直接

据我所知,cmp指令将设置标志寄存器中的一些位。然后,您可以使用诸如jle、jnp等指令来基于这些指令进行分支

我想知道的是如何从比较中恢复整数值

示例:以下是有效的c语法

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)是这样认为的