1.28.4 Setting and clearing specific bits: FPU example
Here is the layout of the bits present in the float type in IEEE 754 format:
Since I don't like passing over something without explaining and interpreting it, let me break this down quickly:
* S (Sign) — the highest bit (Most Significant Bit - MSB). If it is 0 the number is positive, if 1 the number is negative.
* Exponent — 8 bits that determine the power of 2 for the number, but in a biased representation (add 127).
* Mantissa — 23 bits that define the digits after the decimal point.
So if you want to change the sign of a number from positive to negative or vice versa, all you need to do is flip bit 31 from 0 to 1 or from 1 to 0. The rest of the number (the exponent and mantissa) stays exactly as it is.
Getting back to our topic — the sign of the number lives in the highest bit (MSB). Is it possible to change the sign of a floating point number without any FPU instructions?
#include <stdio.h>
float my_abs (float i){ // clear bit 31 (sign bit) — force the number to be positive unsigned int tmp = (*(unsigned int*)&i) & 0x7FFFFFFF; // AND with mask: all 1s except bit 31 return *(float*)&tmp; // reinterpret the modified bits as float};
float set_sign (float i){ // set bit 31 (sign bit) — force the number to be negative unsigned int tmp = (*(unsigned int*)&i) | 0x80000000; // OR with mask: only bit 31 is 1 return *(float*)&tmp; // reinterpret the modified bits as float};
float negate (float i){ // flip bit 31 (sign bit) — toggle the sign unsigned int tmp = (*(unsigned int*)&i) ^ 0x80000000; // XOR with mask: flips only bit 31 return *(float*)&tmp; // reinterpret the modified bits as float};
int main(){ printf ("my_abs():\n"); printf ("%f\n", my_abs (123.456)); // should print 123.456 printf ("%f\n", my_abs (-456.123)); // should print 456.123 printf ("set_sign():\n"); printf ("%f\n", set_sign (123.456)); // should print -123.456 printf ("%f\n", set_sign (-456.123)); // should print -456.123 printf ("negate():\n"); printf ("%f\n", negate (123.456)); // should print -123.456 printf ("%f\n", negate (-456.123)); // should print 456.123};
We need this trick in C/C++ to copy to and from a float value without any actual conversion. There are three functions: my_abs() clears the MSB (sets it to 0); set_sign() sets the MSB (forces it to 1); and negate() flips it. XOR can be used to flip a bit.
x86
The code is straightforward at this point.
Listing 1.286: Optimizing MSVC 2012
_tmp$ = 8_i$ = 8
_my_abs PROC and DWORD PTR _i$[esp-4], 2147483647 ; AND with 0x7FFFFFFF — clear bit 31 (sign bit) fld DWORD PTR _tmp$[esp-4] ; load the modified float from stack into ST0 (FPU return register) ret 0_my_abs ENDP
_tmp$ = 8_i$ = 8
_set_sign PROC or DWORD PTR _i$[esp-4], -2147483648 ; OR with 0x80000000 — set bit 31 (sign bit) fld DWORD PTR _tmp$[esp-4] ; load the modified float into ST0 ret 0_set_sign ENDP
_tmp$ = 8_i$ = 8
_negate PROC xor DWORD PTR _i$[esp-4], -2147483648 ; XOR with 0x80000000 — flip bit 31 (sign bit) fld DWORD PTR _tmp$[esp-4] ; load the modified float into ST0 ret 0_negate ENDPA quick explanation:
* DWORD PTR _i$[esp-4] — points to the first parameter on the stack (because in Win32 the function receives a float on the stack).
* and ... 0x7FFFFFFF — modifies the bit directly in memory.
* fld — loads the modified value from its new location into the FPU register ST0 in order to return it.
AND clears and OR sets the desired bit. XOR flips it. Finally, the modified value is loaded into ST0, because floating point values are returned in that register.
Now let's try MSVC 2012 optimizing for x64:
tmp$ = 8i$ = 8
my_abs PROC movss DWORD PTR [rsp+8], xmm0 ; spill float argument from XMM0 onto stack mov eax, DWORD PTR i$[rsp] ; load the 32-bit representation into EAX btr eax, 31 ; BTR: test bit 31 then reset (clear) it mov DWORD PTR tmp$[rsp], eax ; store modified value back onto stack movss xmm0, DWORD PTR tmp$[rsp] ; load modified float into XMM0 (return register in Win64) ret 0my_abs ENDP
set_sign PROC movss DWORD PTR [rsp+8], xmm0 ; spill float argument from XMM0 onto stack mov eax, DWORD PTR i$[rsp] ; load the 32-bit representation into EAX bts eax, 31 ; BTS: test bit 31 then set it mov DWORD PTR tmp$[rsp], eax ; store modified value back onto stack movss xmm0, DWORD PTR tmp$[rsp] ; load modified float into XMM0 ret 0set_sign ENDP
negate PROC movss DWORD PTR [rsp+8], xmm0 ; spill float argument from XMM0 onto stack mov eax, DWORD PTR i$[rsp] ; load the 32-bit representation into EAX btc eax, 31 ; BTC: test bit 31 then complement (flip) it mov DWORD PTR tmp$[rsp], eax ; store modified value back onto stack movss xmm0, DWORD PTR tmp$[rsp] ; load modified float into XMM0 ret 0negate ENDP
Here the first float parameter arrives in xmm0 and is temporarily copied to the local stack. Then three new instructions appear:
* BTR (Bit Test and Reset) — tests the bit value (puts it in the Carry Flag), then clears it (sets it to zero). A shorthand for AND with an inverted mask.
* BTS (Bit Test and Set) — tests then sets the bit.
* BTC (Bit Test and Complement) — tests then flips the bit.
Finally, the result is copied into XMM0, because floating point values are returned through XMM0 in the Win64 environment.
MIPS
my_abs: ; move from coprocessor 1 (FPU) to integer register: mfc1 $v1, $f12 ; $v1 = float argument from FPU register $f12 li $v0, 0x7FFFFFFF ; $v0 = 0x7FFFFFFF (mask to clear sign bit) and $v0, $v1 ; $v0 = $v1 & 0x7FFFFFFF — clear bit 31 mtc1 $v0, $f0 ; move result back to FPU register $f0 (return value) jr $ra ; return or $at, $zero ; branch delay slot (NOP)
set_sign: ; move from coprocessor 1 (FPU) to integer register: mfc1 $v0, $f12 ; $v0 = float argument from FPU register $f12 lui $v1, 0x8000 ; $v1 = 0x80000000 (load upper immediate: 0x8000 << 16) or $v0, $v1, $v0 ; $v0 = $v0 | 0x80000000 — set bit 31 mtc1 $v0, $f0 ; move result back to FPU register $f0 jr $ra ; return or $at, $zero ; branch delay slot (NOP)
negate: ; move from coprocessor 1 (FPU) to integer register: mfc1 $v0, $f12 ; $v0 = float argument from FPU register $f12 lui $v1, 0x8000 ; $v1 = 0x80000000 xor $v0, $v1, $v0 ; $v0 = $v0 ^ 0x80000000 — flip bit 31 mtc1 $v0, $f0 ; move result back to FPU register $f0 jr $ra ; return or $at, $zero ; branch delay slot (NOP)GCC 4.4.5 for MIPS does roughly the same thing:
Listing 1.288: Optimizing GCC 4.4.5 (IDA)
my_abs: mfc1 $v1, $f12 ; Move From Coprocessor 1: transfer float from $f12 to integer $v1 li $v0, 0x7FFFFFFF ; load mask 0x7FFFFFFF into $v0 and $v0, $v1 ; $v0 = float_bits & 0x7FFFFFFF — clear sign bit mtc1 $v0, $f0 ; Move To Coprocessor 1: transfer result to FPU return register $f0 jr $ra ; return or $at, $zero ; branch delay slot (NOP)
set_sign: mfc1 $v0, $f12 ; transfer float from $f12 to integer $v0 lui $v1, 0x8000 ; $v1 = 0x8000 << 16 = 0x80000000 or $v0, $v1, $v0 ; $v0 = float_bits | 0x80000000 — set sign bit mtc1 $v0, $f0 ; transfer result to FPU return register $f0 jr $ra ; return or $at, $zero ; branch delay slot (NOP)
negate: mfc1 $v0, $f12 ; transfer float from $f12 to integer $v0 lui $v1, 0x8000 ; $v1 = 0x80000000 xor $v0, $v1, $v0 ; $v0 = float_bits ^ 0x80000000 — flip sign bit mtc1 $v0, $f0 ; transfer result to FPU return register $f0 jr $ra ; return or $at, $zero ; branch delay slot (NOP)A quick explanation:
* mfc1 $v1, $f12 — Move From Coprocessor 1. Transfers from $f12 (first parameter) to $v1 (integer CPU register).
* The modification is done using and/or/xor with the appropriate mask.
* mtc1 $v0, $f0 — Move To Coprocessor 1. Returns the result in $f0 (FPU return register).
Loading 0x80000000 is done with lui $v1, 0x8000 (Load Upper Immediate): it places 0x8000 in the upper 16 bits and zeros the lower 16 bits, producing 0x80000000 because 0x8000 << 16 = 0x80000000. This saves us from needing an extra ori.
ARM
Optimizing Keil 6/2013 (ARM mode)
my_abs PROC BIC r0, r0, #0x80000000 ; clear bit 31 — BIC = Bitwise bit Clear (AND with inverted mask) BX lr ; return ENDP
set_sign PROC ORR r0, r0, #0x80000000 ; set bit 31 — ORR = logical OR BX lr ; return ENDP
negate PROC EOR r0, r0, #0x80000000 ; flip bit 31 — EOR = Exclusive OR (ARM name for XOR) BX lr ; return ENDP
All good so far. ARM has the BIC instruction that clears specific bits explicitly. EOR is the ARM instruction name for the XOR operation ("Exclusive OR").
Optimizing Keil 6/2013 (Thumb mode)
my_abs PROC LSLS r0, r0, #1 ; R0 = i << 1 (shifts out the sign bit MSB) LSRS r0, r0, #1 ; R0 = (i << 1) >> 1 (shifts back, MSB is now 0) BX lr ; return ENDP
set_sign PROC MOVS r1, #1 ; R1 = 1 LSLS r1, r1, #31 ; R1 = 1 << 31 = 0x80000000 (build the mask) ORRS r0, r0, r1 ; R0 = R0 | 0x80000000 — set bit 31 BX lr ; return ENDP
negate PROC MOVS r1, #1 ; R1 = 1 LSLS r1, r1, #31 ; R1 = 1 << 31 = 0x80000000 (build the mask) EORS r0, r0, r1 ; R0 = R0 ^ 0x80000000 — flip bit 31 BX lr ; return ENDP
Thumb mode in ARM offers 16-bit instructions, and not much data can be encoded in them, so the MOVS/LSLS instruction pair is used here to build the constant 0x80000000. It works like this: 1 << 31 = 0x80000000.
The my_abs code looks strange — it is effectively doing (i << 1) >> 1. That expression seems meaningless. But when input << 1 executes, the MSB (the sign bit) gets dropped. When the result is then >> 1, all bits shift back to their positions, but the MSB is now zero, because all "new" bits that appear from shift operations are always zeros. So the LSLS/LSRS pair effectively clears the MSB.
Optimizing GCC 4.6.3 (Raspberry Pi, ARM mode)
Listing 1.291: Optimizing GCC 4.6.3 for Raspberry Pi (ARM mode)
my_abs: FMRS R2, S0 ; copy float from FPU register S0 to integer register R2 BIC R3, R2, #0x80000000 ; clear bit 31 — R3 = R2 & ~0x80000000 FMSR S0, R3 ; copy result from R3 back to FPU register S0 (return value) BX LR ; return
set_sign: FMRS R2, S0 ; copy float from FPU register S0 to integer register R2 ORR R3, R2, #0x80000000 ; set bit 31 — R3 = R2 | 0x80000000 FMSR S0, R3 ; copy result back to FPU register S0 BX LR ; return
negate: FMRS R2, S0 ; copy float from FPU register S0 to integer register R2 ADD R3, R2, #0x80000000 ; flip bit 31 using ADD — equivalent to XOR here (see explanation below) FMSR S0, R3 ; copy result back to FPU register S0 BX LR ; returnWe are running Raspberry Pi Linux in QEMU which emulates the ARM FPU, so S-registers are used here for floating point numbers instead of R-registers.
The FMRS instruction moves data between GPR (general purpose registers) and the FPU and vice versa.
my_abs() and set_sign() work as expected, but what about negate()? Why is there ADD instead of XOR?
Hard to believe, but the instruction ADD register, 0x80000000 works exactly like XOR register, 0x80000000. First, what is our goal? The goal is to flip the MSB. Forget XOR for a moment. From school math we may remember that adding values like 1000 to other numbers does not affect the last 3 digits at all. Example: 1234567 + 10000 = 1244567 (the last 4 digits are unaffected).
But here we are working in binary, and 0x80000000 is 0b10000000000000000000000000000000 — meaning only the highest bit is set.
Adding 0x80000000 to any value never affects the lower 31 bits — it only affects the MSB. Adding 1 to 0 gives 1. Adding 1 to 1 gives 0b10 in binary, but bit 32 (counting from zero) gets dropped because our registers are 32 bits wide, so the result becomes 0. That is why XOR can be replaced by ADD here. It is hard to say why GCC decided to do it this way, but it works correctly.
1.28.5 Counting bits set to 1
This is a simple example of a function that counts the number of bits whose value is 1 in the input value.
These operations are also called "population count".
#include <stdio.h>#define IS_SET(flag, bit) ((flag) & (bit)) // macro: checks if a specific bit is set in flag using bitwise AND
int f(unsigned int a){ int i; int rt = 0; // bit counter, starts at 0 for (i = 0; i < 32; i++) // iterate over all 32 bit positions if (IS_SET(a, 1 << i)) // check if bit i is set in a rt++; // if set, increment counter return rt; // return total count of set bits}
int main(){ f(0x12345678); // test call}
In this loop, the counter i counts from 0 to 31, so 1 << i produces values from 1 to 0x80000000. To describe this operation in natural language, we say "shift 1 left by n bits". In other words, 1 << i sequentially produces every possible bit position in a 32-bit number. The bit that is freed on the right is always zeroed (set to 0).
| C/C++ expression | Power of 2 | Decimal | Hexadecimal |
|---|---|---|---|
| 1 << 0 | 2^0 | 1 | 1 |
| 1 << 1 | 2^1 | 2 | 2 |
| 1 << 2 | 2^2 | 4 | 4 |
| 1 << 3 | 2^3 | 8 | 8 |
| 1 << 4 | 2^4 | 16 | 0x10 |
| 1 << 5 | 2^5 | 32 | 0x20 |
| 1 << 6 | 2^6 | 64 | 0x40 |
| 1 << 7 | 2^7 | 128 | 0x80 |
| 1 << 8 | 2^8 | 256 | 0x100 |
| 1 << 9 | 2^9 | 512 | 0x200 |
| 1 << 10 | 2^10 | 1024 | 0x400 |
| 1 << 11 | 2^11 | 2048 | 0x800 |
| 1 << 12 | 2^12 | 4096 | 0x1000 |
| 1 << 13 | 2^13 | 8192 | 0x2000 |
| 1 << 14 | 2^14 | 16384 | 0x4000 |
| 1 << 15 | 2^15 | 32768 | 0x8000 |
| 1 << 16 | 2^16 | 65536 | 0x10000 |
| 1 << 17 | 2^17 | 131072 | 0x20000 |
| 1 << 18 | 2^18 | 262144 | 0x40000 |
| 1 << 19 | 2^19 | 524288 | 0x80000 |
| 1 << 20 | 2^20 | 1048576 | 0x100000 |
| 1 << 21 | 2^21 | 2097152 | 0x200000 |
| 1 << 22 | 2^22 | 4194304 | 0x400000 |
| 1 << 23 | 2^23 | 8388608 | 0x800000 |
| 1 << 24 | 2^24 | 16777216 | 0x1000000 |
| 1 << 25 | 2^25 | 33554432 | 0x2000000 |
| 1 << 26 | 2^26 | 67108864 | 0x4000000 |
| 1 << 27 | 2^27 | 134217728 | 0x8000000 |
| 1 << 28 | 2^28 | 268435456 | 0x10000000 |
| 1 << 29 | 2^29 | 536870912 | 0x20000000 |
| 1 << 30 | 2^30 | 1073741824 | 0x40000000 |
| 1 << 31 | 2^31 | 2147483648 | 0x80000000 |
These constants (bit masks) appear very frequently in code, and the reverse engineer must be able to recognize them quickly. Decimal numbers below 65536 and hexadecimal ones are very easy to memorize. Decimal numbers above 65536 are generally not worth memorizing. These constants are used very heavily to set flags for specific bits. Example: a portion of ssl_private.h from Apache 2.4.6 source code:
#define SSL_OPT_NONE (0) // no options set#define SSL_OPT_RELSET (1<<0) // bit 0: relative set#define SSL_OPT_STDENVVARS (1<<1) // bit 1: standard environment variables#define SSL_OPT_EXPORTCERTDATA (1<<3) // bit 3: export certificate data#define SSL_OPT_FAKEBASICAUTH (1<<4) // bit 4: fake basic auth#define SSL_OPT_STRICTREQUIRE (1<<5) // bit 5: strict require#define SSL_OPT_OPTRENEGOTIATE (1<<6) // bit 6: optional renegotiation#define SSL_OPT_LEGACYDNFORMAT (1<<7) // bit 7: legacy DN formatLet's return to our example.
The IS_SET macro checks for the presence of a bit in a. The IS_SET macro is in fact an AND operation (logical AND) and returns 0 if the specified bit is not present, or returns the bit mask itself if the bit is present. The if statement in C/C++ fires if the expression inside it is non-zero (it can even be 123456), and that is why it always works correctly.
x86
MSVC
Listing 1.292: MSVC 2010
_rt$ = -8 ; size = 4 bytes_i$ = -4 ; size = 4 bytes_a$ = 8 ; size = 4 bytes
_f PROC push ebp mov ebp, esp sub esp, 8 ; allocate 8 bytes on stack for rt and i mov DWORD PTR _rt$[ebp], 0 ; rt = 0 mov DWORD PTR _i$[ebp], 0 ; i = 0 jmp SHORT $LN4@f ; jump to loop condition check$LN3@f: mov eax, DWORD PTR _i$[ebp] ; load i into EAX add eax, 1 ; EAX = i + 1 mov DWORD PTR _i$[ebp], eax ; i++$LN4@f: cmp DWORD PTR _i$[ebp], 32 ; compare i with 32 (0x20) jge SHORT $LN2@f ; if i >= 32, exit loop mov edx, 1 ; EDX = 1 (base bit mask) mov ecx, DWORD PTR _i$[ebp] ; ECX = i (shift amount) shl edx, cl ; EDX = 1 << i (build the bit mask) and edx, DWORD PTR _a$[ebp] ; EDX = mask & a (test if bit i is set) je SHORT $LN1@f ; AND result was 0? skip increment mov eax, DWORD PTR _rt$[ebp] ; load rt add eax, 1 ; rt + 1 mov DWORD PTR _rt$[ebp], eax ; rt++$LN1@f: jmp SHORT $LN3@f ; loop back to increment$LN2@f: mov eax, DWORD PTR _rt$[ebp] ; return rt in EAX mov esp, ebp pop ebp ret 0_f ENDPx32dbg
We will run the above example in x32dbg, using the value 0x12345678 (God willing I will explain it in detail so everything is clear and easy).
Let's step into the first iteration when i = 0, and see how i is loaded into ECX:
The first instruction mov edx, 1 places the value 1 into EDX — this is the mask for bit zero.
Then the instruction mov ecx, dword ptr ss:[ebp-0x04] loads the value of i into ECX, which is 0 since this is the beginning of the loop.
Then the instruction shl edx, cl — the most important one (Shift Logical Left) — shifts the value of EDX (which is 1) left by the number of bits in CL (the low byte of ECX, containing i=0). Result: 1 << 0 = 1.
So EDX is still 1, and it is the mask that will check bit zero.
After that it will execute je.
When i = 1, we see how i is loaded into ECX:
EDX holds 1. And SHL will now execute.
EDX now holds 1 << 1 (i.e. 2). This is the bit mask as we said before.
The AND sets ZF to 1, which means that the input value (0x12345678) ANDed with 2:
So, there is no corresponding bit set in the input value.
The part of the code that increments the counter will not execute: the JZ instruction skips it.
Let's trace a bit more. Now i = 4. SHL will execute:
EDX = 1 << 4 (i.e. 0x10 or 16):
Another mask.
When the AND executes:
ZF became 0 because this bit is present in the input value.
Indeed, 0x12345678 & 0x10 = 0x10. This bit gets counted: the jump does not take place, and the bit counter is incremented.
The function returns 13. That is the total count of bits set to 1 in 0x12345678.
GCC
Let's compile it in GCC 4.4.1:
Listing 1.293: GCC 4.4.1
public ff proc nearrt = dword ptr -0Ch ; local variable: bit counteri = dword ptr -8 ; local variable: loop indexarg_0 = dword ptr 8 ; function argument: input value a
push ebp mov ebp, esp push ebx ; save EBX (callee-saved register) sub esp, 10h ; allocate 16 bytes on stack mov [ebp+rt], 0 ; rt = 0 mov [ebp+i], 0 ; i = 0 jmp short loc_80483EF ; jump to loop condition checkloc_80483D0: mov eax, [ebp+i] ; load i into EAX (shift amount) mov edx, 1 ; EDX = 1 (base value for mask) mov ebx, edx ; EBX = 1 (copy to working register) mov ecx, eax ; ECX = i (shift amount) shl ebx, cl ; EBX = 1 << i (build the bit mask) mov eax, ebx ; EAX = 1 << i and eax, [ebp+arg_0] ; EAX = mask & a (test if bit i is set) test eax, eax ; is EAX zero? jz short loc_80483EB ; if zero, skip increment add [ebp+rt], 1 ; rt++loc_80483EB: add [ebp+i], 1 ; i++loc_80483EF: cmp [ebp+i], 1Fh ; compare i with 31 (0x1F) jle short loc_80483D0 ; if i <= 31, continue loop mov eax, [ebp+rt] ; return rt in EAX add esp, 10h pop ebx ; restore EBX pop ebp retnf endpx64
Let's modify the example slightly to extend it to 64-bit:
#include <stdio.h>#include <stdint.h>#define IS_SET(flag, bit) ((flag) & (bit)) // macro: tests whether a specific bit is set
int f(uint64_t a){ uint64_t i; int rt = 0; // bit counter for (i = 0; i < 64; i++) // iterate over all 64 bit positions if (IS_SET(a, 1ULL << i)) // 1ULL ensures a 64-bit shift rt++; return rt;}Non-optimizing GCC 4.8.2
Listing 1.294: Non-optimizing GCC 4.8.2
f: push rbp mov rbp, rsp mov QWORD PTR [rbp-24], rdi ; store argument a on stack mov DWORD PTR [rbp-12], 0 ; rt = 0 mov QWORD PTR [rbp-8], 0 ; i = 0 jmp .L2 ; jump to loop condition.L4: mov rax, QWORD PTR [rbp-8] ; RAX = i mov rdx, QWORD PTR [rbp-24] ; RDX = a mov ecx, eax ; ECX = i (shift amount) shr rdx, cl ; RDX = a >> i (shift a right by i bits) mov rax, rdx ; RAX = a >> i and eax, 1 ; EAX = (a>>i) & 1 (isolate lowest bit) test rax, rax ; is lowest bit zero? je .L3 ; if zero, skip increment add DWORD PTR [rbp-12], 1 ; rt++.L3: add QWORD PTR [rbp-8], 1 ; i++.L2: cmp QWORD PTR [rbp-8], 63 ; i <= 63? jbe .L4 ; if yes, jump back to loop body mov eax, DWORD PTR [rbp-12] ; rt into EAX for return pop rbp retOptimizing GCC 4.8.2
Listing 1.295: Optimizing GCC 4.8.2
f: xor eax, eax ; rt lives in EAX, initialize to 0 xor ecx, ecx ; i lives in ECX, initialize to 0.L3: mov rsi, rdi ; load input value (a) lea edx, [rax+1] ; EDX = EAX + 1 (proposed new value of rt) shr rsi, cl ; RSI = RSI >> CL (i.e. a >> i) and esi, 1 ; ESI = (a>>i) & 1 (isolate lowest bit) cmovne eax, edx ; if ZF=0 (bit was set): EAX = EDX (commit rt++) add rcx, 1 ; i++ cmp rcx, 64 ; i == 64? jne .L3 ; if not, repeat loop rep ret ; FATRET — AMD-recommended return after conditional jumpThis code is shorter, but a bit unusual.
In all the examples we have seen so far, we incremented the value of "rt" after comparing a specific bit, but here the code increments "rt" beforehand (line 6), writing the new value into register EDX. This means: if the last bit was 1, the CMOVNE instruction (which is equivalent to CMOVNZ) commits the new value of "rt" by moving EDX ("proposed rt value") into EAX ("the current rt that will be returned").
Consequently, the increment operation happens at every step of the loop — that is, 64 times — regardless of the input value.
The advantage of this code is that it contains only one conditional jump (at the end of the loop) instead of two (one to skip the rt++ increment and one at the end of the loop). This can make it faster on modern processors that have branch predictors: section 2.4.1 on page 575.
The last instruction is REP RET (opcode F3 C3), also called FATRET by MSVC. This is an optimized variant of RET, recommended by AMD to be placed at the end of a function when RET immediately follows a conditional jump.
Optimizing MSVC 2010
Listing 1.296: Optimizing MSVC 2010
a$ = 8f PROC ; RCX = input value xor eax, eax ; rt = 0 mov edx, 1 ; RDX = 1 (initial bit mask) lea r8d, QWORD PTR [rax+64] ; R8D = 64 (loop counter, counts down) npad 5 ; padding for alignment$LL4@f: test rdx, rcx ; is this bit set in the input value? je SHORT $LN3@f ; if not, skip increment inc eax ; rt++$LN3@f: rol rdx, 1 ; RDX rotated left by 1 (cycles the bit, same as SHL here) dec r8 ; R8-- jne SHORT $LL4@f ; if R8 != 0, continue loop fatret 0 ; optimized return (AMD FATRET)f ENDP
Here the ROL instruction is used instead of SHL; it is actually "rotate left" rather than "shift left", but in this example it works exactly like SHL.
R8 counts down from 64 to 0. It is like i but in reverse. This is a table of some register values during execution:
| RDX | R8 |
|---|---|
| 0x0000000000000001 | 64 |
| 0x0000000000000002 | 63 |
| 0x0000000000000004 | 62 |
| 0x0000000000000008 | 61 |
| ... | ... |
| 0x4000000000000000 | 2 |
| 0x8000000000000000 | 1 |
At the end we see the FATRET instruction.
Optimizing MSVC 2012
Listing 1.297: Optimizing MSVC 2012
a$ = 8f PROC ; RCX = input value xor eax, eax mov edx, 1 lea r8d, QWORD PTR [rax+32] ; R8D = 32 (loop runs 32 times, 2 bits per iteration) npad 5$LL4@f: ; first pass -------------------------------- test rdx, rcx ; is bit set in input? je SHORT $LN3@f inc eax ; rt++$LN3@f: rol rdx, 1 ; rotate mask left (advance to next bit) ; second pass -------------------------------- test rdx, rcx ; test the next bit (loop unrolling: 2 bits per iteration) je SHORT $LN11@f inc eax ; rt++$LN11@f: rol rdx, 1 ; rotate mask left again ; ------------------------------------------- dec r8 ; R8-- jne SHORT $LL4@f ; if R8 != 0, continue fatret 0f ENDPOptimizing MSVC 2012 does almost the same work as MSVC 2010, but somehow it generates two identical loop bodies and the iteration count became 32 instead of 64.
Honestly, it is hard to say why. Perhaps an optimization trick? Perhaps a longer loop body is faster? Either way, this code is shown here to demonstrate that compiler output can sometimes be very strange and illogical — yet it works perfectly.
ARM
ARM + Optimizing Xcode 4.6.3 (LLVM) (ARM mode)
Listing 1.298: Optimizing Xcode 4.6.3 (LLVM) (ARM mode)
MOV R1, R0 ; R1 = input value a (preserve it)MOV R0, #0 ; R0 = 0 (rt counter)MOV R2, #1 ; R2 = 1 (base mask value)MOV R3, R0 ; R3 = 0 (i counter)loc_2E54:TST R1, R2, LSL R3 ; set flags based on R1 & (R2 << R3) i.e. a & (1<<i)ADD R3, R3, #1 ; R3++ (i++)ADDNE R0, R0, #1 ; if ZF was cleared by TST (bit was set), increment R0 (rt++)CMP R3, #32 ; compare i with 32BNE loc_2E54 ; if i != 32, continue loopBX LR ; return
TST is the same as TEST in x86.
As we said before, there are no separate shift instructions in ARM mode. Instead there are modifiers such as LSL (Logical Shift Left), LSR (Logical Shift Right), ASR (Arithmetic Shift Right), ROR (Rotate Right), and RRX (Rotate Right with Extend), which can be appended to instructions like MOV, TST, CMP, ADD, SUB, RSB.
These modifiers specify how to shift the second operand and by how many bits. So the instruction TST R1, R2, LSL R3 works here as:
R1 ∧ (R2 ≪ R3) ; bitwise AND of R1 with (R2 shifted left by R3 positions)ARM + Optimizing Xcode 4.6.3 (LLVM) (Thumb-2 mode)
Almost the same thing, but here two instructions LSL.W/TST are used instead of a single TST, because in Thumb mode it is not possible to embed an LSL modifier directly into TST.
MOV R1, R0 ; R1 = input value aMOVS R0, #0 ; R0 = 0 (rt counter)MOV.W R9, #1 ; R9 = 1 (base mask)MOVS R3, #0 ; R3 = 0 (i counter)loc_2F7A:LSL.W R2, R9, R3 ; R2 = R9 << R3 i.e. 1 << i (separate shift, required in Thumb)TST R2, R1 ; set flags based on R2 & R1 i.e. (1<<i) & aADD.W R3, R3, #1 ; i++IT NE ; if-then block: execute next instruction only if NE (ZF=0)ADDNE R0, #1 ; rt++ (only if bit was set)CMP R3, #32 ; compare i with 32BNE loc_2F7A ; if i != 32, continue loopBX LR ; returnARM64 + Optimizing GCC 4.9
Let's take the 64-bit example we used before.
Listing 1.299: Optimizing GCC (Linaro) 4.8
f: mov w2, 0 ; rt = 0 mov x5, 1 ; X5 = 1 (base mask value) mov w1, w2 ; i = 0.L2: lsl x4, x5, x1 ; X4 = X5 << X1 i.e. 1 << i (build 64-bit mask) add w3, w2, 1 ; W3 = rt + 1 (proposed new rt) tst x4, x0 ; (1 << i) & a (test if bit i is set in a) add w1, w1, 1 ; i++ csel w2, w3, w2, ne ; if NE (bit was set): w2 = w3 (rt = new_rt), else w2 = w2 (no change) cmp w1, 64 ; i < 64? bne .L2 ; if not done, repeat loop mov w0, w2 ; rt into W0 for return ret
The CSEL instruction is "Conditional SELect". It selects one of two values based on the flags set by TST, and copies the selected value into W2, which holds the variable rt.
ARM64 + Non-optimizing GCC 4.9
Listing 1.300: Non-optimizing GCC (Linaro) 4.8
f: sub sp, sp, #32 str x0, [sp,8] ; store argument "a" in register save area str wzr, [sp,24] ; rt = 0 str wzr, [sp,28] ; i = 0 b .L2 ; jump to loop condition.L4: ldr w0, [sp,28] ; load i mov x1, 1 ; X1 = 1 lsl x0, x1, x0 ; X0 = X1 << X0 i.e. 1 << i (build mask) mov x1, x0 ; X1 = 1 << i ldr x0, [sp,8] ; X0 = a and x0, x1, x0 ; X0 = (1<<i) & a cmp x0, xzr ; compare X0 with zero beq .L3 ; if zero, skip rt++ ldr w0, [sp,24] ; load rt add w0, w0, 1 ; rt + 1 str w0, [sp,24] ; rt++.L3: ldr w0, [sp,28] ; load i add w0, w0, 1 ; i + 1 str w0, [sp,28] ; i++.L2: ldr w0, [sp,28] ; load i cmp w0, 63 ; i <= 63? ble .L4 ; if yes, jump to loop body ldr w0, [sp,24] ; rt into W0 for return add sp, sp, 32 retMIPS
Non-optimizing GCC
f: ; IDA does not know variable names, we assigned them manually: ; rt = -0x10, i = -0xC, var_4 = -4, a = 0
addiu $sp, -0x18 ; allocate 24 bytes on stack sw $fp, 0x18+var_4($sp) ; save frame pointer move $fp, $sp ; set frame pointer sw $a0, 0x18+a($fp) ; store argument a on stack sw $zero, 0x18+rt($fp) ; rt = 0 sw $zero, 0x18+i($fp) ; i = 0 b loc_68 ; jump to loop condition check or $at, $zero ; branch delay slot (NOP)loc_20: li $v1, 1 ; $v1 = 1 (base mask value) lw $v0, 0x18+i($fp) ; $v0 = i (shift amount) or $at, $zero ; load delay slot (NOP) sllv $v0, $v1, $v0 ; $v0 = 1 << i (SLLV: variable shift amount from register) move $v1, $v0 ; $v1 = mask lw $v0, 0x18+a($fp) ; $v0 = a or $at, $zero ; load delay slot (NOP) and $v0, $v1, $v0 ; $v0 = a & (1<<i) beqz $v0, loc_58 ; if zero (bit not set), skip rt++ or $at, $zero ; branch delay slot (NOP) lw $v0, 0x18+rt($fp) ; load rt or $at, $zero ; load delay slot (NOP) addiu $v0, 1 ; rt + 1 sw $v0, 0x18+rt($fp) ; rt++loc_58: lw $v0, 0x18+i($fp) ; load i or $at, $zero ; load delay slot (NOP) addiu $v0, 1 ; i + 1 sw $v0, 0x18+i($fp) ; i++loc_68: lw $v0, 0x18+i($fp) ; load i or $at, $zero ; load delay slot (NOP) slti $v0, 0x20 ; $v0 = (i < 32) ? 1 : 0 bnez $v0, loc_20 ; if i < 32, loop back or $at, $zero ; branch delay slot (NOP) lw $v0, 0x18+rt($fp) ; return rt move $sp, $fp ; load delay slot: restore stack pointer lw $fp, 0x18+var_4($sp) ; restore frame pointer addiu $sp, 0x18 ; load delay slot: deallocate stack jr $ra ; return to caller or $at, $zero ; branch delay slot (NOP)This code is long: all local variables live in the local stack and are loaded every time they are needed.
The SLLV instruction is "Shift Word Left Logical Variable" — it differs from SLL in that the shift amount in SLL is embedded in the instruction itself (and therefore fixed), whereas SLLV takes the shift amount from a register.
Optimizing GCC
This code is shorter. There are two shift instructions instead of one. Why?
We could replace the first SLLV instruction with an unconditional branch that jumps directly to the second SLLV. But that would be an extra jump instruction in the function, and it is always better to get rid of it.
f: ; $a0 = a ; variable rt will be in $v0: move $v0, $zero ; variable i will be in $v1: move $v1, $zero li $t0, 1 li $a3, 32 sllv $a1, $t0, $v1 ; $a1 = $t0 << $v1 = 1 << iloc_14: and $a1, $a0 ; $a1 = a & (1<<i) ; increment i: addiu $v1, 1 ; jump to loc_28 if a&(1<<i) == 0 and increment rt: beqz $a1, loc_28 addiu $a2, $v0, 1 ; if BEQZ was not triggered, store the updated rt in $v0: move $v0, $a2loc_28: ; if i != 32, jump to loc_14 and prepare the next shifted value: bne $v1, $a3, loc_14 sllv $a1, $t0, $v1 ; return jr $ra or $at, $zero ; branch delay slot, NOP1.28.6 Conclusion
Just like the << and >> operations in C/C++, the shift instructions in x86 are SHR/SHL (for unsigned values) and SAR/SHL (for signed values).
The shift instructions in ARM are LSR/LSL (for unsigned values) and ASR/LSL (for signed values).
It is also possible to add a shift suffix to some instructions (called "data processing instructions").
Checking a specific bit (known at compile time)
Test if bit 0b1000000 (0x40) is present in a register value:
Listing 1.303: C/C++
if (input & 0x40) ...Listing 1.304: x86
TEST REG, 40hJNZ is_set; bit is not setListing 1.305: x86
TEST REG, 40hJZ is_cleared; bit is setListing 1.306: ARM (ARM mode)
TST REG, #0x40BNE is_set; bit is not set
Sometimes, AND is used instead of TEST, but the flags that get set are the same.
Checking a specific bit (determined at runtime)
This is usually done via this C/C++ code snippet (shift the value n bits to the right, then mask the lowest bit):
Listing 1.307: C/C++
if ((value >> n) & 1) ...This is typically implemented in x86 code as:
Listing 1.308: x86
; REG = input_value; CL = nSHR REG, CLAND REG, 1Or (shift 1 bit n times to the left, isolate that bit in the input value and test if it is non-zero):
Listing 1.309: C/C++
if (value & (1 << n)) ...This is typically implemented in x86 code as:
Listing 1.310: x86
; CL = nMOV REG, 1SHL REG, CLAND input_value, REGSetting a specific bit (known at compile time)
Listing 1.311: C/C++
value = value | 0x40;Listing 1.312: x86
OR REG, 40hListing 1.313: ARM (ARM mode) and ARM64
ORR R0, R0, #0x40Setting a specific bit (determined at runtime)
Listing 1.314: C/C++
value = value | (1 << n);This is typically implemented in x86 code as:
Listing 1.315: x86
; CL = nMOV REG, 1SHL REG, CLOR input_value, REGClearing a specific bit (known at compile time)
Simply apply an AND operation with the inverted value:
Listing 1.316: C/C++
value = value & (~0x40);Listing 1.317: x86
AND REG, 0FFFFFFBFhListing 1.318: x64
AND REG, 0FFFFFFFFFFFFFFBFhThis effectively causes all bits to be set except one.
ARM in ARM mode has the BIC instruction, which works like the NOT + AND instruction pair:
Listing 1.319: ARM (ARM mode)
BIC R0, R0, #0x40Clearing a specific bit (determined at runtime)
Listing 1.320: C/C++
value = value & (~(1 << n));Listing 1.321: x86
; CL = nMOV REG, 1SHL REG, CLNOT REGAND input_value, REGIf this article helped you, please share it with others!
Some information may be outdated





