5

I understand that in C-like languages I can perform a boolean not with the not operator: (C)

int myBool = TRUE;
myBool = !myBool;

But my question is: How is this implemented behind the scenes? My guess is by using jumps, but those could be inefficient if used excessively: (Intel x86 syntax)

; assume eax holds boolean
  test eax, eax
  jnz short boolTrue
  inc eax ; eax was 0, now 1
  jmp short after
boolTrue: ; eax non-zero
  xor eax, eax ; eax now 0
after:

As shown, it requires 5 instructions with at least one jump and one bit-wise and (test). There has to be an easier way to do this because I've seen code bases that do "double-nots" (if (!!fileHandle)) for some weird reason.

So (as said above): How do compilers do boolean !s on x86?

Cole Tobin
  • 9,206
  • 15
  • 49
  • 74

4 Answers4

4

You can do it with SETZ (set (if) zero (flag is set)) which require no branches.

MByD
  • 135,866
  • 28
  • 264
  • 277
  • E.g.: `_Bool myBool = TRUE; myBool = !myBool; printf("%d", myBool);` might be compiled to something like `mov eax /*henceforth myBool*/, 1; test eax, eax; setnz eax; push str_format; push eax; call _printf;` if your compiler is *really* naïve. –  Dec 12 '12 at 16:37
  • 1
    I don't think this is correct. I had to use `setz`. The logic is like this: `test eax, eax; setnz eax` means, if `eax` is not zero (aka 1) set `eax` to 1. If `eax` is zero, set `eax` to zero. This just sounds like an identity function is `eax` is zero or one. So, I'm confused how this is correct. – Enrico Borba Jun 13 '17 at 18:50
  • @EnricoBorba - ha.. I think you're right. Now I have some tough questions for past-me. Thanks! – MByD Jun 13 '17 at 18:56
4

There's a possibility of using carry and branchless sequences:

sub %eax, 1        ;; this produces carry only when %eax EQ zero
sbb %eax, %eax     ;; 0 when not carry, -1 when carry
and %eax, 1        ;; just get the least significant bit (also neg %eax will do)

for eax=!eax and

sub %eax, 1        ;; 
sbb %eax, %eax     
add %eax, 1        ;; [-1, 0] + 1 == [0, 1]

for eax = !!eax.

I couldn't figure out if there are 3 instruction (or less) sequences using common arithmetic operations that wouldn't waste the original variable (or register eax).

Aki Suihkonen
  • 19,144
  • 1
  • 36
  • 57
2

In a good compiler, the method for implementing an operator such as NOT will generally not be limited to just one code generation pattern. The compiler will take into account many factors of surrounding context, such as compile time optimizations, source and/or generated code immediately preceding the NOT operator, whether the NOT operator is used in a conditional context (e.g. used in an if-statement) or a value context (used in an assignment).

When used in an if-statement, a NOT operator is more likely to be implemented using a branch, since an if-statement most likely need to branch anyway. When used in a value context, a NOT operator is more likely to be implemented with an compare and capture of the result (without a branch).

When a NOT operator is used in a larger expression, often a nested operator can be reversed, so to one way of looking at it, the NOT operator generates no code at all; this may happen in expressions like a = ! (b > 0) or if ( ! ( a && b ) ). Even with if ( !a ), you'll probably find a isn't actually being NOT'ed, but simply being tested as in if ( a ), though with branch condition reversed.

In the specific example you give, I'd expect a good compiler's best effort (generating code for production and not for debugging), that it would determine at compile time that the constant 0 instead of myBool should be passed to printf.

In short, a decent compiler has many techniques available and many criteria for determining which to use.

Erik Eidt
  • 23,049
  • 2
  • 29
  • 53
-1

If you have a proper boolean, you can use XOR to toggle between the two representations. Note that C doesn't have a bool, it just interprets zero as false and anything else as true. C++ and C# both have proper booleans, as such they don't need to mess with the non-zero check and indeed at least g++ does use the XOR method.

Jester
  • 56,577
  • 4
  • 81
  • 125
  • But what about this: `xor rax, 1`? There will be 8 _bytes_ just for the `1`, so that would be a 10(?) byte instruction – Cole Tobin Dec 12 '12 at 19:54
  • 1
    @ColeJohnson luckily there is `xor eax, 1` which is just 3 bytes (`83 F0 01`) and would work for switching between `0` and `1`. By the way, the only instruction that takes 64 bit immediate is `mov r64, imm64` – Jester Dec 12 '12 at 20:43
  • As a side note, C has `bool` (or rather [`_Bool`](https://stackoverflow.com/questions/1608318/is-bool-a-native-c-type)) since 1999. – Alexis Wilke Mar 12 '23 at 20:20