想象一下同一语言中的两种类型的布尔对,无需费力地

时间:2019-04-09 15:49:15

标签: c assembly

我只有一点理论上的好奇心。 C中的==运算符在正相等的情况下返回1,否则返回0。我的汇编知识非常有限。但是,我想知道理论上是否有可能实现一个新的运算符,该运算符在正等式的情况下返回~0,否则返回0 –但在一个条件下:它必须产生相同的结果==运算符的数量。只是理论上的好奇,我没有实际用途。

编辑

我的问题针对x86 CPU,但是我很想知道是否有本来可以做到这一点的架构。

第二编辑

正如 Sneftel 所指出的,与SETcc指令[1]类似-能够将标志寄存器位转换为0 / {{ 1}}值(而不是传统的~0 / 0)–存在。因此,我的问题的答案似乎是否定的。

第三编辑

一点笔记。我不是试图将逻辑1表示为true,而是试图理解是否可以在需要时将逻辑真值也可选地表达为~0 ,在通常已经将~0表示为true的语言中,无需进一步努力。为此,我假设了一个新运算符,它“返回” 数字,而不是布尔值(由1“返回”的自然逻辑true仍表示为== )–否则我会问是否可以重新设计1而不是==来“返回” ~0。您可以认为此新运算符属于按位运算符系列的一半,该运算符“返回”数字,而不是布尔值(通过布尔值,我不是指布尔数据类型,而是指数字对{{1 }} / 1,这是由于逻辑运算而在C语言中使用的布尔值。

我知道所有这些听起来都是徒劳的,但是我警告过:这是一个理论问题。

但是here我的问题似乎得到了明确解决:

  

某些语言将逻辑位表示为设置了所有位的整数。可以通过为0指令选择逻辑相反的条件,然后递减结果来获得此表示。例如,要测试是否溢出,请使用1指令,然后递减结果。

因此似乎没有直接的指令,因为使用SETcc然后递减意味着要再增加一条指令。

4 个答案:

答案 0 :(得分:3)

编辑:正如其他人指出的那样,那里有一些“有条件分配0/1”的味道。有点破坏了我的观点:)显然,0/1布尔类型比0 /〜0布尔类型接受更深层次的优化。


“运算符返回值”概念是一个高级概念,它没有保留到汇编级。 1/0可能仅作为标志寄存器中的一个位存在,甚至不存在。

换句话说,将相等性运算符的C定义值分配给大小为int的变量在汇编级别不是原语。如果您编写x = (a == b),则编译器可能会将其实现为

cmp a, b ; set the Z flag
cmovz x, 1 ; if equals, assign 1
cmovnz x, 0 ; if not equals, assign 0

或者可以通过条件跳转来完成。如您所见,为TRUE分配一个〜0值将采用相同的命令,只是操作数不同。

我不熟悉的体系结构都没有实现相等性比较,例如“将1或0分配给通用寄存器”。

答案 1 :(得分:2)

没有C运算符的汇编实现。例如,没有x86指令将两个参数进行比较并得出0或1,只有一个x86指令将两个参数进行比较并将结果放入标志寄存器中的某个位。使用==时通常不会发生这种情况。

示例:

void foo(int a, int b) {
    if(a == b) { blah(); }
}

或多或少地产生以下汇编:

foo(int, int):
        cmp     %edi, %esi
        je      .L12
        rep  ret
.L12:
        jmp     blah()

请注意,其中没有任何内容涉及0/1值。如果需要,您必须真正要求它:

int bar(int a, int b) {
    return a == b;
}

变为:

bar(int, int):
        xor     %eax, %eax
        cmp     %edi, %esi
        sete    %al
        ret

我怀疑SETcc指令的存在是促使您提出问题的原因,因为它们将标志寄存器位转换为0/1值。没有相应的指令将它们转换为0 /〜0:GCC会做一个聪明的小DEC来映射它们。但通常,==的结果仅作为抽象的且由优化器确定的两者之间的机器状态差异。

顺便说一句,如果某些x86实现选择将SETcc和后面的DEC融合到一个微型操作程序中,我一点也不感到惊讶。我知道这是通过其他常见指令对完成的。指令流和多个周期之间没有简单的关系。

答案 2 :(得分:1)

只需1个额外的周期,您就可以取反/ output /。

在8086内部,比较操作仅存在于标志中。将标志的值放入变量需要额外的代码。无论您希望true为1还是-1,这几乎都是相同的代码。通常,编译器在评估if语句时实际上不会生成0或1的值,而是直接在比较操作生成的标志上使用Jcc指令。 https://pdos.csail.mit.edu/6.828/2006/readings/i386/Jcc.htm

使用80386,添加了SETcc,SETcc仅将0或1设置为答案,因此,如果代码坚持存储答案,则这是首选设置。 https://pdos.csail.mit.edu/6.828/2006/readings/i386/SETcc.htm

并且有许多新的比较指令将结果保存到以后的寄存器中。这些标志已被视为现代处理器中指令流水线停滞的瓶颈,并且代码优化对它们非常不利。

当然,给定一组要比较的特定值,您可以执行各种技巧来获取0、1或-1。不用说,编译器已被优化为在应用这些技巧时为true生成1,并且在可能的情况下,它实际上根本不存储值,而只是重新组织代码来避免它。

答案 3 :(得分:1)

SIMD向量比较 do 会产生0 / -1结果的向量。在x86 MMX / SSE / AVX,ARM NEON,PowerPC Altivec等上就是这种情况。 (它们是2的补码机,所以我喜欢写-1而不是~0来表示全零/全一的元素。)

例如pcmpeqd xmm0, xmm1xmm0取代xmm0[i] == xmm1[i] ? -1 : 0;的每个元素


这使您可以将它们用作AND掩码,因为SIMD代码无法在不解压缩到标量和返回的情况下分别在每个矢量元素上分支。它必须是无分支的。 How to use if condition in intrinsics

例如在没有SSE4.1 pblendvb / blendvps的情况下根据条件混合两个向量,您将进行比较,然后进行AND / ANDNOT / OR。例如来自Substitute a byte with another one

    __m128i mask = _mm_cmpeq_epi8(inp, val);     // movdqa xmm1, xmm0 / PCMPEQB xmm1, xmm2

    // zero elements in the original where there was a match (that we want to replace)
    inp = _mm_andnot_si128(mask, inp);   // inp &= ~mask;  // PANDN xmm0, xmm1

    //  zero elements where we keep the original
    __m128i tmp = _mm_and_si128(newvals, mask);   // newvals & mask; // PAND xmm3, xmm1

    inp = _mm_or_si128(inp, tmp);             // POR xmm0, xmm1

但是,如果要计算匹配项,则可以减去比较结果。 total -= -1避免了否定向量元素的情况。 How to count character occurrences using SIMD

或者要有条件地添加某些内容,而不是实际进行混合,只需执行total += (x & mask),因为0是ADD之类的操作(以及其他一些诸如XOR和OR)的标识元素。

有关具有内在函数和x86 asm的C语言示例,请参见How to access a char array and change lower case letters to upper case, and vice versaConvert a String In C++ To Upper Case


所有这些与C运算符以及从布尔值到整数的隐式转换无关。

在C和C ++中,运算符返回一个布尔值true / false条件,对于大多数用于标量代码(未自动矢量化)的计算机,在asm中该条件都映射到标志寄存器中的一位。

将其转换为寄存器中的整数是完全独立的事情。


但有趣的是:MIPS没有标志寄存器:它具有一些比较分支指令,用于诸如reg == regreg != reg这样的简单条件(beq和bne)。并以小于零的分支(在一个寄存器的符号位上分支):bltz $reg, target

(还有一个架构$zero寄存器,总是读为零,因此如果reg!= 0或reg == 0,则可以使用该实现分支)。

对于更复杂的条件,您可以使用slt(设置为小于)或sltu(设置为小于无符号)来比较整数寄存器。像slt $t4, $t1, $t0实现t4 = t1 < t0一样,产生0或1。然后可以在是否为0上分支,或在分支之前将多个条件与布尔AND / OR组合。如果您的输入之一是已经为0或1的实际bool,则可以不使用slt对其进行优化。

经典MIPS指令的指令清单不完整(不包括伪指令,例如伪装成bltslt + $at的伪指令:http://www.mrc.uidaho.edu/mrc/people/jff/digital/MIPSir.html

但是MIPS32r6 / MIPS64r6改变了这一点:根据https://en.wikipedia.org/wiki/MIPS_architecture#MIPS32/MIPS64_Release_6,生成真值的指令现在会生成全零或全一,而不仅仅是清除/设置0位。 MIPS32 / 64 r6与以前的MIPS ISA不二进制兼容,它还重新排列了一些操作码。并且由于此更改,甚至无法与asm源兼容!但这是一定的改善。


有趣的是,有一个未记录的8086 SALC指令(从进位设置AL),现代Intel(和AMD?)CPU仍在16/32位模式下提供支持。

基本上就像bne一样,没有设置标志:AL = CF? -1:0。http://os2museum.com/wp/undocumented-8086-opcodes

在x86上用相同的输入两次进行借-减借用做sbb al,al,其中CF是借来的减。 x-x - CF当然总是为零。 (在其他一些ISA(如ARM)上,进位标志的含义与减法相反,C set的意思是“不借用”。)

通常,您可以执行x-x(或所需的任何寄存器)将CF转换为0 / -1整数。但这仅适用于CF。进位标志很特殊,其他标志都没有。

某些AMD CPU甚至认为sbb edx,edx与寄存器的旧值无关,仅取决于CF,例如异或归零。在其他CPU上,它仍然具有相同的体系结构效果,但是对EDX的旧值具有微体系结构虚假依赖。