x86 asm function: 14 bytes of machine code
uint64_t version: 24 bytes
x86-64 SysV calling convention (x
in edi
), but this same machine code will also work in 32bit mode. (Where the lea
will decode as lea eax, [edi + eax*2]
, which gives identical results).
0000000000000040 <onemask_even>:
40: 89 f8 mov eax,edi
42: 25 55 55 55 55 and eax,0x55555555
47: 29 c7 sub edi,eax
49: d1 ef shr edi,1
4b: 8d 04 47 lea eax,[rdi+rax*2]
4e: c3 ret
4f: <end>
0x4f - 0x40
= 14 bytes
This is compiler output from using xnor's excellent mask-once idea the opposite way. (And opposite terminology: the low bit is bit 0, which is even, not odd.)
unsigned onemask_even(unsigned x) {
unsigned emask = ~0U/3;
unsigned e = (x & emask);
return e*2 + ((x - e) >> 1);
}
I didn't find any improvements over what the compiler does. I might have written it as mov eax, 0x555...
/ and eax, edi
, but that's the same length.
The same function for 64bit integers takes 24 bytes (see the godbolt link). I don't see any way shorter than 10-byte movabs rax, 0x55...
to generate the mask in a register. (x86's div
instruction is clunky, so unsigned division of all-ones by 3 doesn't help.)
I did come up with a loop to generate the mask in rax, but it's 10 bytes (exactly the same length as the mov imm64
).
# since 0x55 has its low bit set, shifting it out the top of RAX will set CF
0000000000000000 <swap_bitpairs64>:
0: 31 c0 xor eax,eax ; old garbage in rax could end the loop early
0000000000000002 <swap_bitpairs64.loop>:
2: 48 c1 e0 08 shl rax,0x8
6: b0 55 mov al,0x55 ; set the low byte
8: 73 f8 jnc 2 <swap_bitpairs64.loop> ; loop until CF is set
000000000000000a <swap_bitpairs64.rest_of_function_as_normal>:
# 10 bytes, same as mov rax, 0x5555555555555555
# rax = 0x5555...
a: 48 21 f8 and rax,rdi
...
If we knew that none of the existing bytes in rax
has their low bit set, we could skip the xor
, and this would be 8 bytes long.
A previous version of this answer had a 10 byte loop using the loop
insn, but it had a worst-case run-time of 0xFFFFFFFFFFFFFF08
iterations, because I only set cl
.
unsigned char array_of_bytes[1024]
to work the way you expect (i.e. be a bitfield with 1024 *CHAR_BIT
entries). I'd imagine most answers supporting arbitrary-length inputs would assumeCHAR_BIT
was even, though, since shifting bits between bytes is cumbersome. So you absolutely could put a requirement to supportk
up to some constant size, like 256 or something that's reasonable for AES, and languages without 256bit integer types would have to use loops. That might make SIMD vectors worth considering for an x86 asm answer :P \$\endgroup\$